diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/editing | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/editing')
136 files changed, 9957 insertions, 8019 deletions
diff --git a/Source/WebCore/editing/AlternativeTextController.cpp b/Source/WebCore/editing/AlternativeTextController.cpp index 31ca46ff2..15312a541 100644 --- a/Source/WebCore/editing/AlternativeTextController.cpp +++ b/Source/WebCore/editing/AlternativeTextController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006-2016 Apple Inc. All rights reserved. * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) * * Redistribution and use in source and binary forms, with or without @@ -11,10 +11,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -32,26 +32,28 @@ #include "Editor.h" #include "Element.h" #include "Event.h" -#include "ExceptionCodePlaceholder.h" #include "FloatQuad.h" #include "Frame.h" #include "FrameView.h" #include "Page.h" +#include "RenderedDocumentMarker.h" #include "SpellingCorrectionCommand.h" #include "TextCheckerClient.h" #include "TextCheckingHelper.h" #include "TextEvent.h" +#include "TextIterator.h" #include "VisibleUnits.h" #include "htmlediting.h" #include "markup.h" +#include <wtf/NeverDestroyed.h> namespace WebCore { class AutocorrectionAlternativeDetails : public AlternativeTextDetails { public: - static PassRefPtr<AutocorrectionAlternativeDetails> create(const String& replacementString) + static Ref<AutocorrectionAlternativeDetails> create(const String& replacementString) { - return adoptRef(new AutocorrectionAlternativeDetails(replacementString)); + return adoptRef(*new AutocorrectionAlternativeDetails(replacementString)); } const String& replacementString() const { return m_replacementString; } @@ -65,9 +67,9 @@ private: class DictationAlternativeDetails : public AlternativeTextDetails { public: - static PassRefPtr<DictationAlternativeDetails> create(uint64_t dictationContext) + static Ref<DictationAlternativeDetails> create(uint64_t dictationContext) { - return adoptRef(new DictationAlternativeDetails(dictationContext)); + return adoptRef(*new DictationAlternativeDetails(dictationContext)); } uint64_t dictationContext() const { return m_dictationContext; } @@ -84,35 +86,35 @@ private: static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection() { - DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAutoCorrection, ()); - if (markerTypesForAutoCorrection.isEmpty()) { - markerTypesForAutoCorrection.append(DocumentMarker::Replacement); - markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator); - markerTypesForAutoCorrection.append(DocumentMarker::SpellCheckingExemption); - markerTypesForAutoCorrection.append(DocumentMarker::Autocorrected); + static NeverDestroyed<Vector<DocumentMarker::MarkerType>> markerTypesForAutoCorrection; + if (markerTypesForAutoCorrection.get().isEmpty()) { + markerTypesForAutoCorrection.get().append(DocumentMarker::Replacement); + markerTypesForAutoCorrection.get().append(DocumentMarker::CorrectionIndicator); + markerTypesForAutoCorrection.get().append(DocumentMarker::SpellCheckingExemption); + markerTypesForAutoCorrection.get().append(DocumentMarker::Autocorrected); } return markerTypesForAutoCorrection; } static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement() { - DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ()); - if (markerTypesForReplacement.isEmpty()) { - markerTypesForReplacement.append(DocumentMarker::Replacement); - markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption); + static NeverDestroyed<Vector<DocumentMarker::MarkerType>> markerTypesForReplacement; + if (markerTypesForReplacement.get().isEmpty()) { + markerTypesForReplacement.get().append(DocumentMarker::Replacement); + markerTypesForReplacement.get().append(DocumentMarker::SpellCheckingExemption); } return markerTypesForReplacement; } static const Vector<DocumentMarker::MarkerType>& markerTypesForAppliedDictationAlternative() { - DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAppliedDictationAlternative, ()); - if (markerTypesForAppliedDictationAlternative.isEmpty()) - markerTypesForAppliedDictationAlternative.append(DocumentMarker::SpellCheckingExemption); + static NeverDestroyed<Vector<DocumentMarker::MarkerType>> markerTypesForAppliedDictationAlternative; + if (markerTypesForAppliedDictationAlternative.get().isEmpty()) + markerTypesForAppliedDictationAlternative.get().append(DocumentMarker::SpellCheckingExemption); return markerTypesForAppliedDictationAlternative; } -static bool markersHaveIdenticalDescription(const Vector<DocumentMarker*>& markers) +static bool markersHaveIdenticalDescription(const Vector<RenderedDocumentMarker*>& markers) { if (markers.isEmpty()) return true; @@ -126,7 +128,7 @@ static bool markersHaveIdenticalDescription(const Vector<DocumentMarker*>& marke } AlternativeTextController::AlternativeTextController(Frame& frame) - : m_timer(this, &AlternativeTextController::timerFired) + : m_timer(*this, &AlternativeTextController::timerFired) , m_frame(frame) { } @@ -144,7 +146,7 @@ void AlternativeTextController::startAlternativeTextUITimer(AlternativeTextType // If type is PanelTypeReversion, then the new range has been set. So we shouldn't clear it. if (type == AlternativeTextTypeCorrection) - m_alternativeTextInfo.rangeWithAlternative.clear(); + m_alternativeTextInfo.rangeWithAlternative = nullptr; m_alternativeTextInfo.type = type; m_timer.startOneShot(correctionPanelTimerInterval); } @@ -152,7 +154,7 @@ void AlternativeTextController::startAlternativeTextUITimer(AlternativeTextType void AlternativeTextController::stopAlternativeTextUITimer() { m_timer.stop(); - m_alternativeTextInfo.rangeWithAlternative.clear(); + m_alternativeTextInfo.rangeWithAlternative = nullptr; } void AlternativeTextController::stopPendingCorrection(const VisibleSelection& oldSelection) @@ -180,7 +182,7 @@ void AlternativeTextController::applyPendingCorrection(const VisibleSelection& s if (doApplyCorrection) handleAlternativeTextUIResult(dismissSoon(ReasonForDismissingAlternativeTextAccepted)); else - m_alternativeTextInfo.rangeWithAlternative.clear(); + m_alternativeTextInfo.rangeWithAlternative = nullptr; } bool AlternativeTextController::hasPendingCorrection() const @@ -240,10 +242,7 @@ void AlternativeTextController::applyAlternativeTextToRange(const Range* range, if (!range) return; - ExceptionCode ec = 0; - RefPtr<Range> paragraphRangeContainingCorrection = range->cloneRange(ec); - if (ec) - return; + RefPtr<Range> paragraphRangeContainingCorrection = range->cloneRange(); setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(range->startPosition())); setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(range->endPosition())); @@ -254,25 +253,28 @@ void AlternativeTextController::applyAlternativeTextToRange(const Range* range, // 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 rangeWithAlternative. - RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition()); - if (ec) - return; + RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer().document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition()); - Position startPositionOfrangeWithAlternative = range->startPosition(); - correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfrangeWithAlternative.containerNode(), startPositionOfrangeWithAlternative.computeOffsetInContainerNode(), ec); - if (ec) + Position startPositionOfRangeWithAlternative = range->startPosition(); + if (!startPositionOfRangeWithAlternative.containerNode()) + return; + auto setEndResult = correctionStartOffsetInParagraphAsRange->setEnd(*startPositionOfRangeWithAlternative.containerNode(), startPositionOfRangeWithAlternative.computeOffsetInContainerNode()); + if (setEndResult.hasException()) return; // Take note of the location of autocorrection so that we can add marker after the replacement took place. 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> rangeWithAlternative = range->cloneRange(ec); - - int paragraphStartIndex = TextIterator::rangeLength(Range::create(*m_frame.document(), m_frame.document(), 0, paragraphRangeContainingCorrection.get()->startContainer(), paragraphRangeContainingCorrection.get()->startOffset()).get()); - applyCommand(SpellingCorrectionCommand::create(rangeWithAlternative, alternative)); + Ref<Range> rangeWithAlternative = range->cloneRange(); + + ContainerNode& rootNode = paragraphRangeContainingCorrection.get()->startContainer().treeScope().rootNode(); + int paragraphStartIndex = TextIterator::rangeLength(Range::create(rootNode.document(), &rootNode, 0, ¶graphRangeContainingCorrection->startContainer(), paragraphRangeContainingCorrection->startOffset()).ptr()); + applyCommand(SpellingCorrectionCommand::create(rangeWithAlternative.ptr(), alternative)); // Recalculate pragraphRangeContainingCorrection, since SpellingCorrectionCommand modified the DOM, such that the original paragraphRangeContainingCorrection is no longer valid. Radar: 10305315 Bugzilla: 89526 - paragraphRangeContainingCorrection = TextIterator::rangeFromLocationAndLength(m_frame.document(), paragraphStartIndex, correctionStartOffsetInParagraph + alternative.length()); + paragraphRangeContainingCorrection = TextIterator::rangeFromLocationAndLength(&rootNode, paragraphStartIndex, correctionStartOffsetInParagraph + alternative.length()); + if (!paragraphRangeContainingCorrection) + return; setEnd(paragraphRangeContainingCorrection.get(), m_frame.selection().selection().start()); RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, alternative.length()); @@ -282,10 +284,10 @@ void AlternativeTextController::applyAlternativeTextToRange(const Range* range, if (newText != alternative) return; - DocumentMarkerController& markers = replacementRange->startContainer()->document().markers(); - size_t size = markerTypesToAdd.size(); - for (size_t i = 0; i < size; ++i) - markers.addMarker(replacementRange.get(), markerTypesToAdd[i], markerDescriptionForAppliedAlternativeText(alternativeType, markerTypesToAdd[i])); + DocumentMarkerController& markers = replacementRange->startContainer().document().markers(); + + for (auto& markerType : markerTypesToAdd) + markers.addMarker(replacementRange.get(), markerType, markerDescriptionForAppliedAlternativeText(alternativeType, markerType)); } bool AlternativeTextController::applyAutocorrectionBeforeTypingIfAppropriate() @@ -312,9 +314,11 @@ bool AlternativeTextController::applyAutocorrectionBeforeTypingIfAppropriate() void AlternativeTextController::respondToUnappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction) { if (AlternativeTextClient* client = alternativeTextClient()) - client->recordAutocorrectionResponse(AutocorrectionReverted, corrected, correction); + client->recordAutocorrectionResponse(AutocorrectionResponse::Reverted, corrected, correction); + + Ref<Frame> protector(m_frame); m_frame.document()->updateLayout(); - m_frame.selection().setSelection(selectionOfCorrected, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | FrameSelection::SpellCorrectionTriggered); + m_frame.selection().setSelection(selectionOfCorrected, FrameSelection::defaultSetSelectionOptions() | FrameSelection::SpellCorrectionTriggered); RefPtr<Range> range = Range::create(*m_frame.document(), m_frame.selection().selection().start(), m_frame.selection().selection().end()); DocumentMarkerController& markers = m_frame.document()->markers(); @@ -323,7 +327,7 @@ void AlternativeTextController::respondToUnappliedSpellCorrection(const VisibleS markers.addMarker(range.get(), DocumentMarker::SpellCheckingExemption); } -void AlternativeTextController::timerFired(Timer<AlternativeTextController>&) +void AlternativeTextController::timerFired() { m_isDismissedByEditing = false; switch (m_alternativeTextInfo.type) { @@ -336,13 +340,13 @@ void AlternativeTextController::timerFired(Timer<AlternativeTextController>&) } break; case AlternativeTextTypeReversion: { - if (!m_alternativeTextInfo.rangeWithAlternative) + auto* details = static_cast<const AutocorrectionAlternativeDetails*>(m_alternativeTextInfo.details.get()); + if (!m_alternativeTextInfo.rangeWithAlternative || !details || details->replacementString().isEmpty()) break; 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>()); } @@ -353,9 +357,9 @@ void AlternativeTextController::timerFired(Timer<AlternativeTextController>&) break; String paragraphText = plainText(TextCheckingParagraph(m_alternativeTextInfo.rangeWithAlternative).paragraphRange().get()); Vector<String> suggestions; - textChecker()->getGuessesForWord(m_alternativeTextInfo.originalText, paragraphText, suggestions); + textChecker()->getGuessesForWord(m_alternativeTextInfo.originalText, paragraphText, m_frame.selection().selection(), suggestions); if (suggestions.isEmpty()) { - m_alternativeTextInfo.rangeWithAlternative.clear(); + m_alternativeTextInfo.rangeWithAlternative = nullptr; break; } String topSuggestion = suggestions.first(); @@ -405,7 +409,7 @@ void AlternativeTextController::handleAlternativeTextUIResult(const String& resu if (result.length()) applyAlternativeTextToRange(rangeWithAlternative, result, m_alternativeTextInfo.type, markerTypesForAutocorrection()); else if (!m_isDismissedByEditing) - rangeWithAlternative->startContainer()->document().markers().addMarker(rangeWithAlternative, DocumentMarker::RejectedCorrection, m_alternativeTextInfo.originalText); + rangeWithAlternative->startContainer().document().markers().addMarker(rangeWithAlternative, DocumentMarker::RejectedCorrection, m_alternativeTextInfo.originalText); break; case AlternativeTextTypeReversion: case AlternativeTextTypeSpellingSuggestions: @@ -418,7 +422,7 @@ void AlternativeTextController::handleAlternativeTextUIResult(const String& resu break; } - m_alternativeTextInfo.rangeWithAlternative.clear(); + m_alternativeTextInfo.rangeWithAlternative = nullptr; } bool AlternativeTextController::isAutomaticSpellingCorrectionEnabled() @@ -432,15 +436,14 @@ FloatRect AlternativeTextController::rootViewRectForRange(const Range* range) co if (!view) return FloatRect(); Vector<FloatQuad> textQuads; - range->textQuads(textQuads); + range->absoluteTextQuads(textQuads); FloatRect boundingRect; - size_t size = textQuads.size(); - for (size_t i = 0; i < size; ++i) - boundingRect.unite(textQuads[i].boundingBox()); + for (auto& textQuad : textQuads) + boundingRect.unite(textQuad.boundingBox()); return view->contentsToRootView(IntRect(boundingRect)); } -void AlternativeTextController::respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options) +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 @@ -466,14 +469,9 @@ void AlternativeTextController::respondToChangedSelection(const VisibleSelection return; Node* node = position.containerNode(); - Vector<DocumentMarker*> markers = node->document().markers().markersFor(node); - size_t markerCount = markers.size(); - for (size_t i = 0; i < markerCount; ++i) { - const DocumentMarker* marker = markers[i]; - if (!marker) - continue; - - if (respondToMarkerAtEndOfWord(*marker, position, options)) + for (auto* marker : node->document().markers().markersFor(node)) { + ASSERT(marker); + if (respondToMarkerAtEndOfWord(*marker, position)) break; } } @@ -485,6 +483,8 @@ void AlternativeTextController::respondToAppliedEditing(CompositeEditCommand* co markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(command); m_originalStringForLastDeletedAutocorrection = String(); + + dismiss(ReasonForDismissingAlternativeTextIgnored); } void AlternativeTextController::respondToUnappliedEditing(EditCommandComposition* command) @@ -506,39 +506,32 @@ AlternativeTextClient* AlternativeTextController::alternativeTextClient() EditorClient* AlternativeTextController::editorClient() { - return m_frame.page() ? m_frame.page()->editorClient() : 0; + return m_frame.page() ? &m_frame.page()->editorClient() : nullptr; } TextCheckerClient* AlternativeTextController::textChecker() { if (EditorClient* owner = editorClient()) return owner->textChecker(); - return 0; + return nullptr; } -void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString) +void AlternativeTextController::recordAutocorrectionResponse(AutocorrectionResponse response, const String& replacedString, PassRefPtr<Range> replacementRange) { - if (AlternativeTextClient* client = alternativeTextClient()) - client->recordAutocorrectionResponse(AutocorrectionReverted, replacedString, replacementString); -} - -void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange) -{ - recordAutocorrectionResponseReversed(replacedString, plainText(replacementRange.get())); + if (auto client = alternativeTextClient()) + client->recordAutocorrectionResponse(response, replacedString, plainText(replacementRange.get())); } 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); + changedRange->startContainer().document().markers().removeMarkers(changedRange.get(), DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker); + changedRange->startContainer().document().markers().addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption); } void AlternativeTextController::markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString) { - Vector<DocumentMarker::MarkerType> markerTypesToAdd = markerTypesForAutocorrection(); - DocumentMarkerController& markers = replacedRange->startContainer()->document().markers(); - for (size_t i = 0; i < markerTypesToAdd.size(); ++i) { - DocumentMarker::MarkerType markerType = markerTypesToAdd[i]; + DocumentMarkerController& markers = replacedRange->startContainer().document().markers(); + for (auto& markerType : markerTypesForAutocorrection()) { if (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected) markers.addMarker(replacedRange.get(), markerType, replacedString); else @@ -550,8 +543,8 @@ void AlternativeTextController::recordSpellcheckerResponseForModifiedCorrection( { if (!rangeOfCorrection) return; - DocumentMarkerController& markers = rangeOfCorrection->startContainer()->document().markers(); - Vector<DocumentMarker*> correctedOnceMarkers = markers.markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected); + DocumentMarkerController& markers = rangeOfCorrection->startContainer().document().markers(); + Vector<RenderedDocumentMarker*> correctedOnceMarkers = markers.markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected); if (correctedOnceMarkers.isEmpty()) return; @@ -559,9 +552,9 @@ void AlternativeTextController::recordSpellcheckerResponseForModifiedCorrection( // 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); + client->recordAutocorrectionResponse(AutocorrectionResponse::Reverted, corrected, correction); else - client->recordAutocorrectionResponse(AutocorrectionEdited, corrected, correction); + client->recordAutocorrectionResponse(AutocorrectionResponse::Edited, corrected, correction); } markers.removeMarkers(rangeOfCorrection, DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker); @@ -585,7 +578,7 @@ void AlternativeTextController::markPrecedingWhitespaceForDeletedAutocorrectionA RefPtr<Range> precedingCharacterRange = Range::create(*m_frame.document(), precedingCharacterPosition, endOfSelection); String string = plainText(precedingCharacterRange.get()); - if (string.isEmpty() || !isWhitespace(string[string.length() - 1])) + if (string.isEmpty() || !deprecatedIsEditingWhitespace(string[string.length() - 1])) return; // Mark this whitespace to indicate we have deleted an autocorrection following this @@ -606,14 +599,16 @@ bool AlternativeTextController::processMarkersOnTextToBeReplacedByResult(const T if (markerController.hasMarkers(rangeWithAlternative, DocumentMarker::RejectedCorrection)) return false; + if (markerController.hasMarkers(rangeWithAlternative, DocumentMarker::AcceptedCandidate)) + return false; + Position beginningOfRange = rangeWithAlternative->startPosition(); Position precedingCharacterPosition = beginningOfRange.previous(); RefPtr<Range> precedingCharacterRange = Range::create(*m_frame.document(), precedingCharacterPosition, beginningOfRange); - Vector<DocumentMarker*> markers = markerController.markersInRange(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection); - - for (size_t i = 0; i < markers.size(); ++i) { - if (markers[i]->description() == stringToBeReplaced) + Vector<RenderedDocumentMarker*> markers = markerController.markersInRange(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection); + for (const auto* marker : markers) { + if (marker->description() == stringToBeReplaced) return false; } @@ -625,10 +620,8 @@ bool AlternativeTextController::shouldStartTimerFor(const WebCore::DocumentMarke return (((marker.type() == DocumentMarker::Replacement && !marker.description().isNull()) || marker.type() == DocumentMarker::Spelling || marker.type() == DocumentMarker::DictationAlternatives) && static_cast<int>(marker.endOffset()) == endOffset); } -bool AlternativeTextController::respondToMarkerAtEndOfWord(const DocumentMarker& marker, const Position& endOfWordPosition, FrameSelection::SetSelectionOptions options) +bool AlternativeTextController::respondToMarkerAtEndOfWord(const DocumentMarker& marker, const Position& endOfWordPosition) { - if (options & FrameSelection::DictationTriggered) - return false; if (!shouldStartTimerFor(marker, endOfWordPosition.offsetInContainerNode())) return false; Node* node = endOfWordPosition.containerNode(); @@ -642,7 +635,7 @@ bool AlternativeTextController::respondToMarkerAtEndOfWord(const DocumentMarker& switch (marker.type()) { case DocumentMarker::Spelling: m_alternativeTextInfo.rangeWithAlternative = wordRange; - m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create(""); + m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create(emptyString()); startAlternativeTextUITimer(AlternativeTextTypeSpellingSuggestions); break; case DocumentMarker::Replacement: @@ -651,13 +644,13 @@ bool AlternativeTextController::respondToMarkerAtEndOfWord(const DocumentMarker& startAlternativeTextUITimer(AlternativeTextTypeReversion); break; case DocumentMarker::DictationAlternatives: { - const DictationMarkerDetails* markerDetails = static_cast<const DictationMarkerDetails*>(marker.details()); - if (!markerDetails) + if (!WTF::holds_alternative<DocumentMarker::DictationData>(marker.data())) return false; - if (currentWord != markerDetails->originalText()) + auto& markerData = WTF::get<DocumentMarker::DictationData>(marker.data()); + if (currentWord != markerData.originalText) return false; m_alternativeTextInfo.rangeWithAlternative = wordRange; - m_alternativeTextInfo.details = DictationAlternativeDetails::create(markerDetails->dictationContext()); + m_alternativeTextInfo.details = DictationAlternativeDetails::create(markerData.context); startAlternativeTextUITimer(AlternativeTextTypeDictationAlternatives); } break; @@ -673,7 +666,7 @@ String AlternativeTextController::markerDescriptionForAppliedAlternativeText(Alt if (alternativeTextType != AlternativeTextTypeReversion && alternativeTextType != AlternativeTextTypeDictationAlternatives && (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected)) return m_alternativeTextInfo.originalText; - return ""; + return emptyString(); } #endif @@ -691,32 +684,30 @@ bool AlternativeTextController::insertDictatedText(const String& text, const Vec if (FrameView* view = m_frame.view()) view->disableLayerFlushThrottlingTemporarilyForInteraction(); - RefPtr<TextEvent> event = TextEvent::createForDictation(m_frame.document()->domWindow(), text, dictationAlternatives); + Ref<TextEvent> event = TextEvent::createForDictation(m_frame.document()->domWindow(), text, dictationAlternatives); event->setUnderlyingEvent(triggeringEvent); - target->dispatchEvent(event, IGNORE_EXCEPTION); + target->dispatchEvent(event); return event->defaultHandled(); } -void AlternativeTextController::removeDictationAlternativesForMarker(const DocumentMarker* marker) +void AlternativeTextController::removeDictationAlternativesForMarker(const DocumentMarker& marker) { #if USE(DICTATION_ALTERNATIVES) - DictationMarkerDetails* details = static_cast<DictationMarkerDetails*>(marker->details()); - if (AlternativeTextClient* client = alternativeTextClient()) - client->removeDictationAlternatives(details->dictationContext()); + ASSERT(WTF::holds_alternative<DocumentMarker::DictationData>(marker.data())); + if (auto* client = alternativeTextClient()) + client->removeDictationAlternatives(WTF::get<DocumentMarker::DictationData>(marker.data()).context); #else UNUSED_PARAM(marker); #endif } -Vector<String> AlternativeTextController::dictationAlternativesForMarker(const DocumentMarker* marker) +Vector<String> AlternativeTextController::dictationAlternativesForMarker(const DocumentMarker& marker) { #if USE(DICTATION_ALTERNATIVES) - ASSERT(marker->type() == DocumentMarker::DictationAlternatives); - if (AlternativeTextClient* client = alternativeTextClient()) { - DictationMarkerDetails* details = static_cast<DictationMarkerDetails*>(marker->details()); - return client->dictationAlternatives(details->dictationContext()); - } + ASSERT(marker.type() == DocumentMarker::DictationAlternatives); + if (auto* client = alternativeTextClient()) + return client->dictationAlternatives(WTF::get<DocumentMarker::DictationData>(marker.data()).context); return Vector<String>(); #else UNUSED_PARAM(marker); @@ -729,12 +720,12 @@ void AlternativeTextController::applyDictationAlternative(const String& alternat #if USE(DICTATION_ALTERNATIVES) Editor& editor = m_frame.editor(); RefPtr<Range> selection = editor.selectedRange(); - if (!selection || !editor.shouldInsertText(alternativeString, selection.get(), EditorInsertActionPasted)) + if (!selection || !editor.shouldInsertText(alternativeString, selection.get(), EditorInsertAction::Pasted)) return; - DocumentMarkerController& markers = selection->startContainer()->document().markers(); - Vector<DocumentMarker*> dictationAlternativesMarkers = markers.markersInRange(selection.get(), DocumentMarker::DictationAlternatives); - for (size_t i = 0; i < dictationAlternativesMarkers.size(); ++i) - removeDictationAlternativesForMarker(dictationAlternativesMarkers[i]); + DocumentMarkerController& markers = selection->startContainer().document().markers(); + Vector<RenderedDocumentMarker*> dictationAlternativesMarkers = markers.markersInRange(selection.get(), DocumentMarker::DictationAlternatives); + for (auto* marker : dictationAlternativesMarkers) + removeDictationAlternativesForMarker(*marker); applyAlternativeTextToRange(selection.get(), alternativeString, AlternativeTextTypeDictationAlternatives, markerTypesForAppliedDictationAlternative()); #else diff --git a/Source/WebCore/editing/AlternativeTextController.h b/Source/WebCore/editing/AlternativeTextController.h index c843f603d..c583503ea 100644 --- a/Source/WebCore/editing/AlternativeTextController.h +++ b/Source/WebCore/editing/AlternativeTextController.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AlternativeTextController_h -#define AlternativeTextController_h +#pragma once #include "AlternativeTextClient.h" #include "DocumentMarker.h" @@ -48,7 +47,6 @@ struct DictationAlternative; class AlternativeTextDetails : public RefCounted<AlternativeTextDetails> { public: - AlternativeTextDetails() { } virtual ~AlternativeTextDetails() { } }; @@ -60,24 +58,6 @@ struct AlternativeTextInfo { RefPtr<AlternativeTextDetails> details; }; -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) @@ -108,7 +88,7 @@ public: void respondToUnappliedSpellCorrection(const VisibleSelection&, const String& corrected, const String& correction) UNLESS_ENABLED({ UNUSED_PARAM(corrected); UNUSED_PARAM(correction); }) void respondToAppliedEditing(CompositeEditCommand*) UNLESS_ENABLED({ }) void respondToUnappliedEditing(EditCommandComposition*) UNLESS_ENABLED({ }) - void respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions) UNLESS_ENABLED({ UNUSED_PARAM(oldSelection); }) + void respondToChangedSelection(const VisibleSelection& oldSelection) UNLESS_ENABLED({ UNUSED_PARAM(oldSelection); }) void stopPendingCorrection(const VisibleSelection& oldSelection) UNLESS_ENABLED({ UNUSED_PARAM(oldSelection); }) void applyPendingCorrection(const VisibleSelection& selectionAfterTyping) UNLESS_ENABLED({ UNUSED_PARAM(selectionAfterTyping); }) @@ -121,7 +101,7 @@ public: bool isAutomaticSpellingCorrectionEnabled() UNLESS_ENABLED({ return false; }) bool shouldRemoveMarkersUponEditing(); - void recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange) UNLESS_ENABLED({ UNUSED_PARAM(replacedString); UNUSED_PARAM(replacementRange); }) + void recordAutocorrectionResponse(AutocorrectionResponse, 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); }) @@ -130,21 +110,20 @@ public: void deletedAutocorrectionAtPosition(const Position&, const String& originalString) UNLESS_ENABLED({ UNUSED_PARAM(originalString); }) bool insertDictatedText(const String&, const Vector<DictationAlternative>&, Event*); - void removeDictationAlternativesForMarker(const DocumentMarker*); - Vector<String> dictationAlternativesForMarker(const DocumentMarker*); + void removeDictationAlternativesForMarker(const DocumentMarker&); + Vector<String> dictationAlternativesForMarker(const DocumentMarker&); void applyDictationAlternative(const String& alternativeString); private: #if USE(AUTOCORRECTION_PANEL) String dismissSoon(ReasonForDismissingAlternativeText); void applyAlternativeTextToRange(const Range*, const String& alternative, AlternativeTextType, const Vector<DocumentMarker::MarkerType>&); - void timerFired(Timer<AlternativeTextController>&); - void recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString); + void timerFired(); void recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction); String markerDescriptionForAppliedAlternativeText(AlternativeTextType, DocumentMarker::MarkerType); bool shouldStartTimerFor(const DocumentMarker&, int endOffset) const; - bool respondToMarkerAtEndOfWord(const DocumentMarker&, const Position& endOfWordPosition, FrameSelection::SetSelectionOptions); + bool respondToMarkerAtEndOfWord(const DocumentMarker&, const Position& endOfWordPosition); AlternativeTextClient* alternativeTextClient(); EditorClient* editorClient(); @@ -153,7 +132,7 @@ private: FloatRect rootViewRectForRange(const Range*) const; void markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(EditCommand*); - Timer<AlternativeTextController> m_timer; + Timer m_timer; AlternativeTextInfo m_alternativeTextInfo; bool m_isDismissedByEditing; @@ -176,5 +155,3 @@ inline bool AlternativeTextController::shouldRemoveMarkersUponEditing() } } // namespace WebCore - -#endif // AlternativeTextController_h diff --git a/Source/WebCore/editing/AppendNodeCommand.cpp b/Source/WebCore/editing/AppendNodeCommand.cpp index 25c596a45..a8fb92c3c 100644 --- a/Source/WebCore/editing/AppendNodeCommand.cpp +++ b/Source/WebCore/editing/AppendNodeCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -28,63 +28,44 @@ #include "AXObjectCache.h" #include "Document.h" -#include "ExceptionCodePlaceholder.h" #include "RenderElement.h" +#include "Text.h" #include "htmlediting.h" namespace WebCore { -AppendNodeCommand::AppendNodeCommand(PassRefPtr<ContainerNode> parent, PassRefPtr<Node> node) - : SimpleEditCommand(parent->document()) +AppendNodeCommand::AppendNodeCommand(PassRefPtr<ContainerNode> parent, Ref<Node>&& node, EditAction editingAction) + : SimpleEditCommand(parent->document(), editingAction) , m_parent(parent) - , m_node(node) + , m_node(WTFMove(node)) { ASSERT(m_parent); - ASSERT(m_node); ASSERT(!m_node->parentNode()); ASSERT(m_parent->hasEditableStyle() || !m_parent->renderer()); } -static void sendAXTextChangedIgnoringLineBreaks(Node* node, AXObjectCache::AXTextChange textChange) -{ - String nodeValue = node->nodeValue(); - // Don't consider linebreaks in this command - if (nodeValue == "\n") - return; - - if (AXObjectCache* cache = node->document().existingAXObjectCache()) - cache->nodeTextChangeNotification(node, textChange, 0, nodeValue); -} - void AppendNodeCommand::doApply() { if (!m_parent->hasEditableStyle() && m_parent->renderer()) return; - m_parent->appendChild(m_node.get(), IGNORE_EXCEPTION); - - if (AXObjectCache::accessibilityEnabled()) - sendAXTextChangedIgnoringLineBreaks(m_node.get(), AXObjectCache::AXTextInserted); + m_parent->appendChild(m_node); } void AppendNodeCommand::doUnapply() { if (!m_parent->hasEditableStyle()) return; - - // Need to notify this before actually deleting the text - if (AXObjectCache::accessibilityEnabled()) - sendAXTextChangedIgnoringLineBreaks(m_node.get(), AXObjectCache::AXTextDeleted); - m_node->remove(IGNORE_EXCEPTION); + m_node->remove(); } #ifndef NDEBUG void AppendNodeCommand::getNodesInCommand(HashSet<Node*>& nodes) { addNodeAndDescendants(m_parent.get(), nodes); - addNodeAndDescendants(m_node.get(), nodes); + addNodeAndDescendants(m_node.ptr(), nodes); } #endif diff --git a/Source/WebCore/editing/AppendNodeCommand.h b/Source/WebCore/editing/AppendNodeCommand.h index 397ae2b5a..5d58be7a8 100644 --- a/Source/WebCore/editing/AppendNodeCommand.h +++ b/Source/WebCore/editing/AppendNodeCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AppendNodeCommand_h -#define AppendNodeCommand_h +#pragma once #include "EditCommand.h" @@ -32,25 +31,23 @@ namespace WebCore { class AppendNodeCommand : public SimpleEditCommand { public: - static PassRefPtr<AppendNodeCommand> create(PassRefPtr<ContainerNode> parent, PassRefPtr<Node> node) + static Ref<AppendNodeCommand> create(PassRefPtr<ContainerNode> parent, Ref<Node>&& node, EditAction editingAction) { - return adoptRef(new AppendNodeCommand(parent, node)); + return adoptRef(*new AppendNodeCommand(parent, WTFMove(node), editingAction)); } private: - AppendNodeCommand(PassRefPtr<ContainerNode> parent, PassRefPtr<Node>); + AppendNodeCommand(PassRefPtr<ContainerNode> parent, Ref<Node>&&, EditAction); - virtual void doApply() override; - virtual void doUnapply() override; + void doApply() override; + void doUnapply() override; #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override; + void getNodesInCommand(HashSet<Node*>&) override; #endif RefPtr<ContainerNode> m_parent; - RefPtr<Node> m_node; + Ref<Node> m_node; }; } // namespace WebCore - -#endif // AppendNodeCommand_h diff --git a/Source/WebCore/editing/ApplyBlockElementCommand.cpp b/Source/WebCore/editing/ApplyBlockElementCommand.cpp index 2deb1b46d..6731880ea 100644 --- a/Source/WebCore/editing/ApplyBlockElementCommand.cpp +++ b/Source/WebCore/editing/ApplyBlockElementCommand.cpp @@ -27,12 +27,11 @@ #include "config.h" #include "ApplyBlockElementCommand.h" -#include "HTMLElement.h" +#include "HTMLBRElement.h" #include "HTMLNames.h" #include "RenderElement.h" #include "RenderStyle.h" #include "Text.h" -#include "TextIterator.h" #include "VisibleUnits.h" #include "htmlediting.h" @@ -71,8 +70,12 @@ void ApplyBlockElementCommand::doApply() // FIXME: We paint the gap before some paragraphs that are indented with left // margin/padding, but not others. We should make the gap painting more consistent and // then use a left margin/padding rule here. - if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd)) - setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(CannotCrossEditingBoundary), endingSelection().isDirectional())); + if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd)) { + VisibleSelection newSelection(visibleStart, visibleEnd.previous(CannotCrossEditingBoundary), endingSelection().isDirectional()); + if (newSelection.isNone()) + return; + setEndingSelection(newSelection); + } VisibleSelection selection = selectionForParagraphIteration(endingSelection()); VisiblePosition startOfSelection = selection.visibleStart(); @@ -94,6 +97,11 @@ void ApplyBlockElementCommand::doApply() if (startScope == endScope && startIndex >= 0 && startIndex <= endIndex) { VisiblePosition start(visiblePositionForIndex(startIndex, startScope.get())); VisiblePosition end(visiblePositionForIndex(endIndex, endScope.get())); + // Work around the fact indexForVisiblePosition can return a larger index due to TextIterator + // using an extra newline to represent a large margin. + // FIXME: Add a new TextIteratorBehavior to suppress it. + if (start.isNotNull() && end.isNull()) + end = lastPositionInNode(endScope.get()); if (start.isNotNull() && end.isNotNull()) setEndingSelection(VisibleSelection(start, end, endingSelection().isDirectional())); } @@ -104,12 +112,12 @@ void ApplyBlockElementCommand::formatSelection(const VisiblePosition& startOfSel // Special case empty unsplittable elements because there's nothing to split // and there's nothing to move. Position start = startOfSelection.deepEquivalent().downstream(); - if (isAtUnsplittableElement(start)) { - RefPtr<Element> blockquote = createBlockElement(); - insertNodeAt(blockquote, start); - RefPtr<Element> placeholder = createBreakElement(document()); - appendNode(placeholder, blockquote); - setEndingSelection(VisibleSelection(positionBeforeNode(placeholder.get()), DOWNSTREAM, endingSelection().isDirectional())); + if (isAtUnsplittableElement(start) && startOfParagraph(start) == endOfParagraph(endOfSelection)) { + auto blockquote = createBlockElement(); + insertNodeAt(blockquote.copyRef(), start); + auto placeholder = HTMLBRElement::create(document()); + appendNode(placeholder.copyRef(), WTFMove(blockquote)); + setEndingSelection(VisibleSelection(positionBeforeNode(placeholder.ptr()), DOWNSTREAM, endingSelection().isDirectional())); return; } @@ -127,25 +135,33 @@ void ApplyBlockElementCommand::formatSelection(const VisiblePosition& startOfSel rangeForParagraphSplittingTextNodesIfNeeded(endOfCurrentParagraph, start, end); endOfCurrentParagraph = end; + // FIXME: endOfParagraph can errornously return a position at the beginning of a block element + // when the position passed into endOfParagraph is at the beginning of a block. + // Work around this bug here because too much of the existing code depends on the current behavior of endOfParagraph. + if (start == end && startOfBlock(start) != endOfBlock(start) && !isEndOfBlock(end) && start == startOfParagraph(endOfBlock(start))) { + endOfCurrentParagraph = endOfBlock(end); + end = endOfCurrentParagraph.deepEquivalent(); + } + Position afterEnd = end.next(); Node* enclosingCell = enclosingNodeOfType(start, &isTableCell); - VisiblePosition endOfNextParagraph = endOfNextParagrahSplittingTextNodesIfNeeded(endOfCurrentParagraph, start, end); + VisiblePosition endOfNextParagraph = endOfNextParagraphSplittingTextNodesIfNeeded(endOfCurrentParagraph, start, end); formatRange(start, end, m_endOfLastParagraph, blockquoteForNextIndent); // Don't put the next paragraph in the blockquote we just created for this paragraph unless // the next paragraph is in the same cell. if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell)) - blockquoteForNextIndent = 0; + blockquoteForNextIndent = nullptr; // indentIntoBlockquote could move more than one paragraph if the paragraph // is in a list item or a table. As a result, endAfterSelection could refer to a position // no longer in the document. - if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().anchorNode()->inDocument()) + if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().anchorNode()->isConnected()) break; // Sanity check: Make sure our moveParagraph calls didn't remove endOfNextParagraph.deepEquivalent().deprecatedNode() // If somehow we did, return to prevent crashes. - if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().anchorNode()->inDocument()) { + if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().anchorNode()->isConnected()) { ASSERT_NOT_REACHED(); return; } @@ -157,29 +173,23 @@ static bool isNewLineAtPosition(const Position& position) { Node* textNode = position.containerNode(); int offset = position.offsetInContainerNode(); - if (!textNode || !textNode->isTextNode() || offset < 0 || offset >= textNode->maxCharacterOffset()) + if (!is<Text>(textNode) || offset < 0 || offset >= textNode->maxCharacterOffset()) return false; - - ExceptionCode ec = 0; - String textAtPosition = toText(textNode)->substringData(offset, 1, ec); - if (ec) - return false; - - return textAtPosition[0] == '\n'; + return downcast<Text>(*textNode).data()[offset] == '\n'; } -RenderStyle* ApplyBlockElementCommand::renderStyleOfEnclosingTextNode(const Position& position) +const RenderStyle* ApplyBlockElementCommand::renderStyleOfEnclosingTextNode(const Position& position) { if (position.anchorType() != Position::PositionIsOffsetInAnchor || !position.containerNode() || !position.containerNode()->isTextNode()) - return 0; + return nullptr; document().updateStyleIfNeeded(); RenderObject* renderer = position.containerNode()->renderer(); if (!renderer) - return 0; + return nullptr; return &renderer->style(); } @@ -190,7 +200,7 @@ void ApplyBlockElementCommand::rangeForParagraphSplittingTextNodesIfNeeded(const end = endOfCurrentParagraph.deepEquivalent(); bool isStartAndEndOnSameNode = false; - if (RenderStyle* startStyle = renderStyleOfEnclosingTextNode(start)) { + if (auto* startStyle = renderStyleOfEnclosingTextNode(start)) { isStartAndEndOnSameNode = renderStyleOfEnclosingTextNode(end) && start.containerNode() == end.containerNode(); bool isStartAndEndOfLastParagraphOnSameNode = renderStyleOfEnclosingTextNode(m_endOfLastParagraph) && start.containerNode() == m_endOfLastParagraph.containerNode(); @@ -215,7 +225,7 @@ void ApplyBlockElementCommand::rangeForParagraphSplittingTextNodesIfNeeded(const } } - if (RenderStyle* endStyle = renderStyleOfEnclosingTextNode(end)) { + if (auto* endStyle = renderStyleOfEnclosingTextNode(end)) { bool isEndAndEndOfLastParagraphOnSameNode = renderStyleOfEnclosingTextNode(m_endOfLastParagraph) && end.deprecatedNode() == m_endOfLastParagraph.deprecatedNode(); // Include \n at the end of line if we're at an empty paragraph if (endStyle->preserveNewline() && start == end && end.offsetInContainerNode() < end.containerNode()->maxCharacterOffset()) { @@ -226,8 +236,8 @@ void ApplyBlockElementCommand::rangeForParagraphSplittingTextNodesIfNeeded(const m_endOfLastParagraph = end; } - // If end is in the middle of a text node, split. - if (!endStyle->collapseWhiteSpace() && end.offsetInContainerNode() && end.offsetInContainerNode() < end.containerNode()->maxCharacterOffset()) { + // If end is in the middle of a text node and the text node is editable, split. + if (endStyle->userModify() != READ_ONLY && !endStyle->collapseWhiteSpace() && end.offsetInContainerNode() && end.offsetInContainerNode() < end.containerNode()->maxCharacterOffset()) { RefPtr<Text> endContainer = end.containerText(); splitTextNode(endContainer, end.offsetInContainerNode()); if (isStartAndEndOnSameNode) @@ -236,18 +246,18 @@ void ApplyBlockElementCommand::rangeForParagraphSplittingTextNodesIfNeeded(const if (m_endOfLastParagraph.offsetInContainerNode() == end.offsetInContainerNode()) m_endOfLastParagraph = lastPositionInOrAfterNode(endContainer->previousSibling()); else - m_endOfLastParagraph = Position(endContainer, m_endOfLastParagraph.offsetInContainerNode() - end.offsetInContainerNode()); + m_endOfLastParagraph = Position(endContainer.get(), m_endOfLastParagraph.offsetInContainerNode() - end.offsetInContainerNode()); } end = lastPositionInNode(endContainer->previousSibling()); } } } -VisiblePosition ApplyBlockElementCommand::endOfNextParagrahSplittingTextNodesIfNeeded(VisiblePosition& endOfCurrentParagraph, Position& start, Position& end) +VisiblePosition ApplyBlockElementCommand::endOfNextParagraphSplittingTextNodesIfNeeded(VisiblePosition& endOfCurrentParagraph, Position& start, Position& end) { VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); Position position = endOfNextParagraph.deepEquivalent(); - RenderStyle* style = renderStyleOfEnclosingTextNode(position); + auto* style = renderStyleOfEnclosingTextNode(position); if (!style) return endOfNextParagraph; @@ -260,20 +270,20 @@ VisiblePosition ApplyBlockElementCommand::endOfNextParagrahSplittingTextNodesIfN // Avoid this by splitting "\n" splitTextNode(text, 1); - if (text == start.containerNode() && text->previousSibling() && text->previousSibling()->isTextNode()) { + if (text == start.containerNode() && is<Text>(text->previousSibling())) { ASSERT(start.offsetInContainerNode() < position.offsetInContainerNode()); - start = Position(toText(text->previousSibling()), start.offsetInContainerNode()); + start = Position(downcast<Text>(text->previousSibling()), start.offsetInContainerNode()); } - if (text == end.containerNode() && text->previousSibling() && text->previousSibling()->isTextNode()) { + if (text == end.containerNode() && is<Text>(text->previousSibling())) { ASSERT(end.offsetInContainerNode() < position.offsetInContainerNode()); - end = Position(toText(text->previousSibling()), end.offsetInContainerNode()); + end = Position(downcast<Text>(text->previousSibling()), end.offsetInContainerNode()); } if (text == m_endOfLastParagraph.containerNode()) { if (m_endOfLastParagraph.offsetInContainerNode() < position.offsetInContainerNode()) { // We can only fix endOfLastParagraph if the previous node was still text and hasn't been modified by script. - if (text->previousSibling()->isTextNode() - && static_cast<unsigned>(m_endOfLastParagraph.offsetInContainerNode()) <= toText(text->previousSibling())->length()) - m_endOfLastParagraph = Position(toText(text->previousSibling()), m_endOfLastParagraph.offsetInContainerNode()); + if (is<Text>(*text->previousSibling()) + && static_cast<unsigned>(m_endOfLastParagraph.offsetInContainerNode()) <= downcast<Text>(text->previousSibling())->length()) + m_endOfLastParagraph = Position(downcast<Text>(text->previousSibling()), m_endOfLastParagraph.offsetInContainerNode()); } else m_endOfLastParagraph = Position(text.get(), m_endOfLastParagraph.offsetInContainerNode() - 1); } @@ -281,12 +291,12 @@ VisiblePosition ApplyBlockElementCommand::endOfNextParagrahSplittingTextNodesIfN return Position(text.get(), position.offsetInContainerNode() - 1); } -PassRefPtr<Element> ApplyBlockElementCommand::createBlockElement() +Ref<HTMLElement> ApplyBlockElementCommand::createBlockElement() { - RefPtr<Element> element = createHTMLElement(document(), m_tagName); + auto element = createHTMLElement(document(), m_tagName); if (m_inlineStyle.length()) element->setAttribute(styleAttr, m_inlineStyle); - return element.release(); + return element; } } diff --git a/Source/WebCore/editing/ApplyBlockElementCommand.h b/Source/WebCore/editing/ApplyBlockElementCommand.h index cd9986d9c..6cc6df497 100644 --- a/Source/WebCore/editing/ApplyBlockElementCommand.h +++ b/Source/WebCore/editing/ApplyBlockElementCommand.h @@ -28,8 +28,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ApplyBlockElementCommand_h -#define ApplyBlockElementCommand_h +#pragma once #include "CompositeEditCommand.h" #include "QualifiedName.h" @@ -42,21 +41,19 @@ protected: ApplyBlockElementCommand(Document&, const QualifiedName& tagName); virtual void formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection); - PassRefPtr<Element> createBlockElement(); + Ref<HTMLElement> createBlockElement(); const QualifiedName tagName() const { return m_tagName; } private: - virtual void doApply() override; + void doApply() override; virtual void formatRange(const Position& start, const Position& end, const Position& endOfSelection, RefPtr<Element>&) = 0; - RenderStyle* renderStyleOfEnclosingTextNode(const Position&); + const RenderStyle* renderStyleOfEnclosingTextNode(const Position&); void rangeForParagraphSplittingTextNodesIfNeeded(const VisiblePosition&, Position&, Position&); - VisiblePosition endOfNextParagrahSplittingTextNodesIfNeeded(VisiblePosition&, Position&, Position&); + VisiblePosition endOfNextParagraphSplittingTextNodesIfNeeded(VisiblePosition&, Position&, Position&); QualifiedName m_tagName; AtomicString m_inlineStyle; Position m_endOfLastParagraph; }; -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/editing/ApplyStyleCommand.cpp b/Source/WebCore/editing/ApplyStyleCommand.cpp index 9e1bfe92f..efc1f8839 100644 --- a/Source/WebCore/editing/ApplyStyleCommand.cpp +++ b/Source/WebCore/editing/ApplyStyleCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -34,8 +34,10 @@ #include "ElementIterator.h" #include "Frame.h" #include "HTMLFontElement.h" +#include "HTMLIFrameElement.h" #include "HTMLInterchange.h" #include "HTMLNames.h" +#include "HTMLSpanElement.h" #include "NodeList.h" #include "NodeTraversal.h" #include "RenderObject.h" @@ -43,9 +45,11 @@ #include "StyleProperties.h" #include "StyleResolver.h" #include "Text.h" +#include "TextIterator.h" #include "TextNodeTraversal.h" #include "VisibleUnits.h" #include "htmlediting.h" +#include <wtf/NeverDestroyed.h> #include <wtf/StdLibExtras.h> #include <wtf/text/StringBuilder.h> @@ -55,22 +59,21 @@ using namespace HTMLNames; static int toIdentifier(PassRefPtr<CSSValue> value) { - return (value && value->isPrimitiveValue()) ? static_pointer_cast<CSSPrimitiveValue>(value)->getValueID() : 0; + return (value && value->isPrimitiveValue()) ? static_pointer_cast<CSSPrimitiveValue>(value)->valueID() : 0; } static String& styleSpanClassString() { - DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass))); + static NeverDestroyed<String> styleSpanClassString(AppleStyleSpanClass); return styleSpanClassString; } -bool isLegacyAppleStyleSpan(const Node *node) +bool isLegacyAppleStyleSpan(const Node* node) { - if (!node || !node->isHTMLElement()) + if (!is<HTMLSpanElement>(node)) return false; - const HTMLElement* elem = toHTMLElement(node); - return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString(); + return downcast<HTMLSpanElement>(*node).attributeWithoutSynchronization(classAttr) == styleSpanClassString(); } static bool hasNoAttributeOrOnlyStyleAttribute(const StyledElement* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty) @@ -79,7 +82,7 @@ static bool hasNoAttributeOrOnlyStyleAttribute(const StyledElement* element, Sho return true; unsigned matchedAttributes = 0; - if (element->getAttribute(classAttr) == styleSpanClassString()) + if (element->attributeWithoutSynchronization(classAttr) == styleSpanClassString()) matchedAttributes++; if (element->hasAttribute(styleAttr) && (shouldStyleAttributeBeEmpty == AllowNonEmptyStyleAttribute || !element->inlineStyle() || element->inlineStyle()->isEmpty())) @@ -91,87 +94,77 @@ static bool hasNoAttributeOrOnlyStyleAttribute(const StyledElement* element, Sho bool isStyleSpanOrSpanWithOnlyStyleAttribute(const Element* element) { - if (!element || !element->hasTagName(spanTag)) + if (!is<HTMLSpanElement>(element)) return false; - return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), AllowNonEmptyStyleAttribute); + return hasNoAttributeOrOnlyStyleAttribute(downcast<HTMLSpanElement>(element), AllowNonEmptyStyleAttribute); } static inline bool isSpanWithoutAttributesOrUnstyledStyleSpan(const Element* element) { - if (!element || !element->isHTMLElement() || !element->hasTagName(spanTag)) + if (!is<HTMLSpanElement>(element)) return false; - return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), StyleAttributeShouldBeEmpty); + return hasNoAttributeOrOnlyStyleAttribute(downcast<HTMLSpanElement>(element), StyleAttributeShouldBeEmpty); } bool isEmptyFontTag(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty) { - if (!element || !element->hasTagName(fontTag)) + if (!is<HTMLFontElement>(element)) return false; - return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), shouldStyleAttributeBeEmpty); + return hasNoAttributeOrOnlyStyleAttribute(downcast<HTMLFontElement>(element), shouldStyleAttributeBeEmpty); } -static PassRefPtr<Element> createFontElement(Document& document) +static RefPtr<HTMLElement> createFontElement(Document& document) { return createHTMLElement(document, fontTag); } -PassRefPtr<HTMLElement> createStyleSpanElement(Document& document) +RefPtr<HTMLElement> createStyleSpanElement(Document& document) { return createHTMLElement(document, spanTag); } ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, EditAction editingAction, EPropertyLevel propertyLevel) - : CompositeEditCommand(document) + : CompositeEditCommand(document, editingAction) , m_style(style->copy()) - , m_editingAction(editingAction) , m_propertyLevel(propertyLevel) , m_start(endingSelection().start().downstream()) , m_end(endingSelection().end().upstream()) , m_useEndingSelection(true) - , m_styledInlineElement(0) , m_removeOnly(false) - , m_isInlineElementToRemoveFunction(0) { } ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel) - : CompositeEditCommand(document) + : CompositeEditCommand(document, editingAction) , m_style(style->copy()) - , m_editingAction(editingAction) , m_propertyLevel(propertyLevel) , m_start(start) , m_end(end) , m_useEndingSelection(false) - , m_styledInlineElement(0) , m_removeOnly(false) - , m_isInlineElementToRemoveFunction(0) { } ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnly, EditAction editingAction) - : CompositeEditCommand(element->document()) + : CompositeEditCommand(element->document(), editingAction) , m_style(EditingStyle::create()) - , m_editingAction(editingAction) , m_propertyLevel(PropertyDefault) , m_start(endingSelection().start().downstream()) , m_end(endingSelection().end().upstream()) , m_useEndingSelection(true) , m_styledInlineElement(element) , m_removeOnly(removeOnly) - , m_isInlineElementToRemoveFunction(0) { } ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction editingAction) - : CompositeEditCommand(document) + : CompositeEditCommand(document, editingAction) , m_style(style->copy()) - , m_editingAction(editingAction) , m_propertyLevel(PropertyDefault) , m_start(endingSelection().start().downstream()) , m_end(endingSelection().end().upstream()) , m_useEndingSelection(true) - , m_styledInlineElement(0) , m_removeOnly(true) , m_isInlineElementToRemoveFunction(isInlineElementToRemoveFunction) { @@ -228,11 +221,6 @@ void ApplyStyleCommand::doApply() } } -EditAction ApplyStyleCommand::editingAction() const -{ - return m_editingAction; -} - void ApplyStyleCommand::applyBlockStyle(EditingStyle *style) { // update document layout once before removing styles @@ -255,18 +243,10 @@ void ApplyStyleCommand::applyBlockStyle(EditingStyle *style) if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan()) return; -#if !PLATFORM(IOS) - // Save and restore the selection endpoints using their indices in the document, since -#else // Save and restore the selection endpoints using their indices in the editable root, since -#endif // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints. // Calculate start and end indices from the start of the tree that they're in. -#if !PLATFORM(IOS) - Node* scope = highestAncestor(visibleStart.deepEquivalent().deprecatedNode()); -#else - Node* scope = highestEditableRoot(visibleStart.deepEquivalent()); -#endif + auto* scope = highestEditableRoot(visibleStart.deepEquivalent()); if (!scope) return; @@ -282,18 +262,18 @@ void ApplyStyleCommand::applyBlockStyle(EditingStyle *style) VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next()); while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) { StyleChange styleChange(style, paragraphStart.deepEquivalent()); - if (styleChange.cssStyle().length() || m_removeOnly) { + if (styleChange.cssStyle() || m_removeOnly) { RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode()); if (!m_removeOnly) { RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent()); if (newBlock) block = newBlock; } - ASSERT(!block || block->isHTMLElement()); - if (block && block->isHTMLElement()) { - removeCSSStyle(style, toHTMLElement(block.get())); + ASSERT(!block || is<HTMLElement>(*block)); + if (is<HTMLElement>(block.get())) { + removeCSSStyle(style, downcast<HTMLElement>(block.get())); if (!m_removeOnly) - addBlockStyle(styleChange, toHTMLElement(block.get())); + addBlockStyle(styleChange, downcast<HTMLElement>(block.get())); } if (nextParagraphStart.isOrphan()) @@ -304,8 +284,8 @@ void ApplyStyleCommand::applyBlockStyle(EditingStyle *style) nextParagraphStart = endOfParagraph(paragraphStart).next(); } - startRange = TextIterator::rangeFromLocationAndLength(toContainerNode(scope), startIndex, 0, true); - endRange = TextIterator::rangeFromLocationAndLength(toContainerNode(scope), endIndex, 0, true); + startRange = TextIterator::rangeFromLocationAndLength(scope, startIndex, 0, true); + endRange = TextIterator::rangeFromLocationAndLength(scope, endIndex, 0, true); if (startRange && endRange) updateStartEnd(startRange->startPosition(), endRange->startPosition()); } @@ -333,7 +313,7 @@ void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style) } // Join up any adjacent text nodes. - if (start.deprecatedNode()->isTextNode()) { + if (is<Text>(*start.deprecatedNode())) { joinChildTextNodes(start.deprecatedNode()->parentNode(), start, end); start = startPosition(); end = endPosition(); @@ -342,7 +322,7 @@ void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style) if (start.isNull() || end.isNull()) return; - if (end.deprecatedNode()->isTextNode() && start.deprecatedNode()->parentNode() != end.deprecatedNode()->parentNode()) { + if (is<Text>(*end.deprecatedNode()) && start.deprecatedNode()->parentNode() != end.deprecatedNode()->parentNode()) { joinChildTextNodes(end.deprecatedNode()->parentNode(), start, end); start = startPosition(); end = endPosition(); @@ -358,49 +338,69 @@ void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style) end = endPosition(); } + if (start.isNull() || end.isNull()) + return; + if (isValidCaretPositionInTextNode(end)) { splitTextAtEnd(start, end); start = startPosition(); end = endPosition(); } + if (start.isNull() || end.isNull()) + return; + // Calculate loop end point. // If the end node is before the start node (can only happen if the end node is // an ancestor of the start node), we gather nodes up to the next sibling of the end node - Node *beyondEnd; - if (start.deprecatedNode()->isDescendantOf(end.deprecatedNode())) - beyondEnd = NodeTraversal::nextSkippingChildren(end.deprecatedNode()); + Node* beyondEnd; + ASSERT(start.deprecatedNode()); + ASSERT(end.deprecatedNode()); + if (start.deprecatedNode()->isDescendantOf(*end.deprecatedNode())) + beyondEnd = NodeTraversal::nextSkippingChildren(*end.deprecatedNode()); else - beyondEnd = NodeTraversal::next(end.deprecatedNode()); + beyondEnd = NodeTraversal::next(*end.deprecatedNode()); start = start.upstream(); // Move upstream to ensure we do not add redundant spans. Node* startNode = start.deprecatedNode(); - if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters. - startNode = NodeTraversal::next(startNode); + + // Make sure we're not already at the end or the next NodeTraversal::next() will traverse past it. + if (startNode == beyondEnd) + return; + + if (is<Text>(*startNode) && start.deprecatedEditingOffset() >= caretMaxOffset(*startNode)) { + // Move out of text node if range does not include its characters. + startNode = NodeTraversal::next(*startNode); + if (!startNode) + return; + } // Store away font size before making any changes to the document. // This ensures that changes to one node won't effect another. HashMap<Node*, float> startingFontSizes; - for (Node *node = startNode; node != beyondEnd; node = NodeTraversal::next(node)) + for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(*node)) { + ASSERT(node); startingFontSizes.set(node, computedFontSize(node)); + } // These spans were added by us. If empty after font size changes, they can be removed. Vector<RefPtr<HTMLElement>> unstyledSpans; - Node* lastStyledNode = 0; - for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(node)) { + Node* lastStyledNode = nullptr; + for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(*node)) { + ASSERT(node); RefPtr<HTMLElement> element; - if (node->isHTMLElement()) { + if (is<HTMLElement>(*node)) { // Only work on fully selected nodes. - if (!nodeFullySelected(node, start, end)) + if (!nodeFullySelected(downcast<HTMLElement>(*node), start, end)) continue; - element = toHTMLElement(node); - } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) { + element = &downcast<HTMLElement>(*node); + } else if (is<Text>(*node) && node->renderer() && node->parentNode() != lastStyledNode) { // Last styled node was not parent node of this text node, but we wish to style this // text node. To make this possible, add a style span to surround this text node. - RefPtr<HTMLElement> span = createStyleSpanElement(document()); + auto span = createStyleSpanElement(document()); surroundNodeRangeWithElement(node, node, span.get()); - element = span.release(); + element = WTFMove(span); } else { // Only handle HTML elements and text nodes. continue; @@ -416,27 +416,26 @@ void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style) currentFontSize = computedFontSize(node); } if (currentFontSize != desiredFontSize) { - inlineStyle->setProperty(CSSPropertyFontSize, cssValuePool().createValue(desiredFontSize, CSSPrimitiveValue::CSS_PX), false); + inlineStyle->setProperty(CSSPropertyFontSize, CSSValuePool::singleton().createValue(desiredFontSize, CSSPrimitiveValue::CSS_PX), false); setNodeAttribute(element.get(), styleAttr, inlineStyle->asText()); } if (inlineStyle->isEmpty()) { removeNodeAttribute(element.get(), styleAttr); if (isSpanWithoutAttributesOrUnstyledStyleSpan(element.get())) - unstyledSpans.append(element.release()); + unstyledSpans.append(WTFMove(element)); } } - size_t size = unstyledSpans.size(); - for (size_t i = 0; i < size; ++i) - removeNodePreservingChildren(unstyledSpans[i].get()); + for (auto& unstyledSpan : unstyledSpans) + removeNodePreservingChildren(unstyledSpan.get()); } static ContainerNode* dummySpanAncestorForNode(const Node* node) { - while (node && (!node->isElementNode() || !isStyleSpanOrSpanWithOnlyStyleAttribute(toElement(node)))) + while (node && (!is<Element>(*node) || !isStyleSpanOrSpanWithOnlyStyleAttribute(downcast<Element>(node)))) node = node->parentNode(); - return node ? node->parentNode() : 0; + return node ? node->parentNode() : nullptr; } void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(ContainerNode* dummySpanAncestor) @@ -454,20 +453,21 @@ void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(ContainerNode* dummySpanA if (isSpanWithoutAttributesOrUnstyledStyleSpan(&child)) toRemove.append(&child); } - for (unsigned i = 0; i < toRemove.size(); ++i) - removeNodePreservingChildren(toRemove[i]); + + for (auto& element : toRemove) + removeNodePreservingChildren(element); } HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, WritingDirection allowedDirection) { // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection. // In that case, we return the unsplit ancestor. Otherwise, we return 0. - Node* block = enclosingBlock(node); - if (!block) + Element* block = enclosingBlock(node); + if (!block || block == node) return 0; - Node* highestAncestorWithUnicodeBidi = 0; - Node* nextHighestAncestorWithUnicodeBidi = 0; + Node* highestAncestorWithUnicodeBidi = nullptr; + Node* nextHighestAncestorWithUnicodeBidi = nullptr; int highestAncestorUnicodeBidi = 0; for (Node* n = node->parentNode(); n != block; n = n->parentNode()) { int unicodeBidi = toIdentifier(ComputedStyleExtractor(n).propertyValue(CSSPropertyUnicodeBidi)); @@ -481,25 +481,25 @@ HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool b if (!highestAncestorWithUnicodeBidi) return 0; - HTMLElement* unsplitAncestor = 0; + HTMLElement* unsplitAncestor = nullptr; WritingDirection highestAncestorDirection; if (allowedDirection != NaturalWritingDirection && highestAncestorUnicodeBidi != CSSValueBidiOverride - && highestAncestorWithUnicodeBidi->isHTMLElement() + && is<HTMLElement>(*highestAncestorWithUnicodeBidi) && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::AllProperties)->textDirection(highestAncestorDirection) && highestAncestorDirection == allowedDirection) { if (!nextHighestAncestorWithUnicodeBidi) - return toHTMLElement(highestAncestorWithUnicodeBidi); + return downcast<HTMLElement>(highestAncestorWithUnicodeBidi); - unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi); + unsplitAncestor = downcast<HTMLElement>(highestAncestorWithUnicodeBidi); highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi; } // Split every ancestor through highest ancestor with embedding. RefPtr<Node> currentNode = node; while (currentNode) { - RefPtr<Element> parent = toElement(currentNode->parentNode()); + RefPtr<Element> parent = downcast<Element>(currentNode->parentNode()); if (before ? currentNode->previousSibling() : currentNode->nextSibling()) splitElement(parent, before ? currentNode : currentNode->nextSibling()); if (parent == highestAncestorWithUnicodeBidi) @@ -511,18 +511,18 @@ HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool b void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor) { - Node* block = enclosingBlock(node); - if (!block) + Element* block = enclosingBlock(node); + if (!block || block == node) return; - Node* parent = 0; - for (Node* n = node->parentNode(); n != block && n != unsplitAncestor; n = parent) { - parent = n->parentNode(); - if (!n->isStyledElement()) + Node* parent = nullptr; + for (Node* ancestor = node->parentNode(); ancestor != block && ancestor != unsplitAncestor; ancestor = parent) { + parent = ancestor->parentNode(); + if (!is<StyledElement>(*ancestor)) continue; - StyledElement* element = toStyledElement(n); - int unicodeBidi = toIdentifier(ComputedStyleExtractor(element).propertyValue(CSSPropertyUnicodeBidi)); + StyledElement& element = downcast<StyledElement>(*ancestor); + int unicodeBidi = toIdentifier(ComputedStyleExtractor(&element).propertyValue(CSSPropertyUnicodeBidi)); if (!unicodeBidi || unicodeBidi == CSSValueNormal) continue; @@ -530,17 +530,17 @@ void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsp // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'. // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and // otherwise it sets the property in the inline style declaration. - if (element->hasAttribute(dirAttr)) { + if (element.hasAttributeWithoutSynchronization(dirAttr)) { // FIXME: If this is a BDO element, we should probably just remove it if it has no // other attributes, like we (should) do with B and I elements. - removeNodeAttribute(element, dirAttr); + removeNodeAttribute(&element, dirAttr); } else { - RefPtr<MutableStyleProperties> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle()); + RefPtr<MutableStyleProperties> inlineStyle = copyStyleOrCreateEmpty(element.inlineStyle()); inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal); inlineStyle->removeProperty(CSSPropertyDirection); - setNodeAttribute(element, styleAttr, inlineStyle->asText()); - if (isSpanWithoutAttributesOrUnstyledStyleSpan(element)) - removeNodePreservingChildren(element); + setNodeAttribute(&element, styleAttr, inlineStyle->asText()); + if (isSpanWithoutAttributesOrUnstyledStyleSpan(&element)) + removeNodePreservingChildren(&element); } } } @@ -557,8 +557,8 @@ static Node* highestEmbeddingAncestor(Node* startNode, Node* enclosingNode) void ApplyStyleCommand::applyInlineStyle(EditingStyle* style) { - RefPtr<ContainerNode> startDummySpanAncestor = 0; - RefPtr<ContainerNode> endDummySpanAncestor = 0; + RefPtr<ContainerNode> startDummySpanAncestor; + RefPtr<ContainerNode> endDummySpanAncestor; // update document layout once before removing styles // so that we avoid the expense of updating before each and every call @@ -590,6 +590,9 @@ void ApplyStyleCommand::applyInlineStyle(EditingStyle* style) startDummySpanAncestor = dummySpanAncestorForNode(start.deprecatedNode()); } + if (start.isNull() || end.isNull()) + return; + // split the end node and containing element if the selection ends inside of it bool splitEnd = isValidCaretPositionInTextNode(end); if (splitEnd) { @@ -602,6 +605,9 @@ void ApplyStyleCommand::applyInlineStyle(EditingStyle* style) endDummySpanAncestor = dummySpanAncestorForNode(end.deprecatedNode()); } + if (start.isNull() || end.isNull()) + return; + // Remove style from the selection. // Use the upstream position of the start for removing style. // This will ensure we remove all traces of the relevant styles from the selection @@ -621,11 +627,11 @@ void ApplyStyleCommand::applyInlineStyle(EditingStyle* style) // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors. Position embeddingRemoveStart = removeStart; - if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end)) + if (startUnsplitAncestor && nodeFullySelected(*startUnsplitAncestor, removeStart, end)) embeddingRemoveStart = positionInParentAfterNode(startUnsplitAncestor); Position embeddingRemoveEnd = end; - if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end)) + if (endUnsplitAncestor && nodeFullySelected(*endUnsplitAncestor, removeStart, end)) embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream(); if (embeddingRemoveEnd != removeStart || embeddingRemoveEnd != end) { @@ -654,6 +660,9 @@ void ApplyStyleCommand::applyInlineStyle(EditingStyle* style) end = endPosition(); } + if (start.isNull() || end.isNull()) + return; + // update document layout once before running the rest of the function // so that we avoid the expense of updating before each and every call // to check a computed style @@ -692,42 +701,43 @@ void ApplyStyleCommand::fixRangeAndApplyInlineStyle(EditingStyle* style, const P { Node* startNode = start.deprecatedNode(); - if (start.deprecatedEditingOffset() >= caretMaxOffset(start.deprecatedNode())) { - startNode = NodeTraversal::next(startNode); + if (start.deprecatedEditingOffset() >= caretMaxOffset(*startNode)) { + startNode = NodeTraversal::next(*startNode); if (!startNode || comparePositions(end, firstPositionInOrBeforeNode(startNode)) < 0) return; } Node* pastEndNode = end.deprecatedNode(); - if (end.deprecatedEditingOffset() >= caretMaxOffset(end.deprecatedNode())) - pastEndNode = NodeTraversal::nextSkippingChildren(end.deprecatedNode()); + if (end.deprecatedEditingOffset() >= caretMaxOffset(*pastEndNode)) + pastEndNode = NodeTraversal::nextSkippingChildren(*pastEndNode); // FIXME: Callers should perform this operation on a Range that includes the br // if they want style applied to the empty line. + // FIXME: Should this be using startNode instead of start.deprecatedNode()? if (start == end && start.deprecatedNode()->hasTagName(brTag)) - pastEndNode = NodeTraversal::next(start.deprecatedNode()); + pastEndNode = NodeTraversal::next(*start.deprecatedNode()); // Start from the highest fully selected ancestor so that we can modify the fully selected node. // e.g. When applying font-size: large on <font color="blue">hello</font>, we need to include the font element in our run // to generate <font color="blue" size="4">hello</font> instead of <font color="blue"><font size="4">hello</font></font> - RefPtr<Range> range = Range::create(startNode->document(), start, end); - Element* editableRoot = startNode->rootEditableElement(); + auto range = Range::create(startNode->document(), start, end); + auto* editableRoot = startNode->rootEditableElement(); if (startNode != editableRoot) { - while (editableRoot && startNode->parentNode() != editableRoot && isNodeVisiblyContainedWithin(startNode->parentNode(), range.get())) + while (editableRoot && startNode->parentNode() != editableRoot && isNodeVisiblyContainedWithin(*startNode->parentNode(), range)) startNode = startNode->parentNode(); } applyInlineStyleToNodeRange(style, startNode, pastEndNode); } -static bool containsNonEditableRegion(Node* node) +static bool containsNonEditableRegion(Node& node) { - if (!node->hasEditableStyle()) + if (!node.hasEditableStyle()) return true; Node* sibling = NodeTraversal::nextSkippingChildren(node); - for (Node* descendent = node->firstChild(); descendent && descendent != sibling; descendent = NodeTraversal::next(descendent)) { - if (!descendent->hasEditableStyle()) + for (Node* descendant = node.firstChild(); descendant && descendant != sibling; descendant = NodeTraversal::next(*descendant)) { + if (!descendant->hasEditableStyle()) return true; } @@ -745,7 +755,7 @@ struct InlineRunToApplyStyle { bool startAndEndAreStillInDocument() { - return start && end && start->inDocument() && end->inDocument(); + return start && end && start->isConnected() && end->isConnected(); } RefPtr<Node> start; @@ -766,35 +776,35 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, PassRef Vector<InlineRunToApplyStyle> runs; RefPtr<Node> node = startNode; for (RefPtr<Node> next; node && node != pastEndNode; node = next) { - next = NodeTraversal::next(node.get()); + next = NodeTraversal::next(*node); if (!node->renderer() || !node->hasEditableStyle()) continue; - if (!node->hasRichlyEditableStyle() && node->isHTMLElement()) { + if (!node->hasRichlyEditableStyle() && is<HTMLElement>(*node)) { // 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.get())) + if (pastEndNode && pastEndNode->isDescendantOf(*node)) break; // Add to this element's inline style and skip over its contents. - HTMLElement* element = toHTMLElement(node.get()); - RefPtr<MutableStyleProperties> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle()); + HTMLElement& element = downcast<HTMLElement>(*node); + RefPtr<MutableStyleProperties> inlineStyle = copyStyleOrCreateEmpty(element.inlineStyle()); if (MutableStyleProperties* otherStyle = style->style()) inlineStyle->mergeAndOverrideOnConflict(*otherStyle); - setNodeAttribute(element, styleAttr, inlineStyle->asText()); - next = NodeTraversal::nextSkippingChildren(node.get()); + setNodeAttribute(&element, styleAttr, inlineStyle->asText()); + next = NodeTraversal::nextSkippingChildren(*node); continue; } if (isBlock(node.get())) continue; - if (node->childNodeCount()) { - if (node->contains(pastEndNode.get()) || containsNonEditableRegion(node.get()) || !node->parentNode()->hasEditableStyle()) + if (node->hasChildNodes()) { + if (node->contains(pastEndNode.get()) || containsNonEditableRegion(*node) || !node->parentNode()->hasEditableStyle()) continue; - if (editingIgnoresContent(node.get())) { - next = NodeTraversal::nextSkippingChildren(node.get()); + if (editingIgnoresContent(*node)) { + next = NodeTraversal::nextSkippingChildren(*node); continue; } } @@ -802,37 +812,35 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, PassRef Node* runStart = node.get(); Node* runEnd = node.get(); Node* sibling = node->nextSibling(); - while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode.get()) - && (!isBlock(sibling) || sibling->hasTagName(brTag)) - && !containsNonEditableRegion(sibling)) { + while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode.get()) && (!isBlock(sibling) || sibling->hasTagName(brTag)) && !containsNonEditableRegion(*sibling)) { runEnd = sibling; sibling = runEnd->nextSibling(); } - next = NodeTraversal::nextSkippingChildren(runEnd); + next = NodeTraversal::nextSkippingChildren(*runEnd); - Node* pastEndNode = NodeTraversal::nextSkippingChildren(runEnd); + Node* pastEndNode = NodeTraversal::nextSkippingChildren(*runEnd); if (!shouldApplyInlineStyleToRun(style, runStart, pastEndNode)) continue; runs.append(InlineRunToApplyStyle(runStart, runEnd, pastEndNode)); } - for (size_t i = 0; i < runs.size(); i++) { - removeConflictingInlineStyleFromRun(style, runs[i].start, runs[i].end, runs[i].pastEndNode); - runs[i].positionForStyleComputation = positionToComputeInlineStyleChange(runs[i].start, runs[i].dummyElement); + for (auto& run : runs) { + removeConflictingInlineStyleFromRun(style, run.start, run.end, run.pastEndNode); + if (run.startAndEndAreStillInDocument()) + run.positionForStyleComputation = positionToComputeInlineStyleChange(run.start, run.dummyElement); } document().updateLayoutIgnorePendingStylesheets(); - for (size_t i = 0; i < runs.size(); i++) - runs[i].change = StyleChange(style, runs[i].positionForStyleComputation); + for (auto& run : runs) + run.change = StyleChange(style, run.positionForStyleComputation); - for (size_t i = 0; i < runs.size(); i++) { - InlineRunToApplyStyle& run = runs[i]; + for (auto& run : runs) { if (run.dummyElement) removeNode(run.dummyElement); if (run.startAndEndAreStillInDocument()) - applyInlineStyleChange(run.start.release(), run.end.release(), run.change, AddStyledElement); + applyInlineStyleChange(WTFMove(run.start), WTFMove(run.end), run.change, AddStyledElement); } } @@ -846,13 +854,13 @@ bool ApplyStyleCommand::shouldApplyInlineStyleToRun(EditingStyle* style, Node* r { ASSERT(style && runStart); - for (Node* node = runStart; node && node != pastEndNode; node = NodeTraversal::next(node)) { - if (node->childNodeCount()) + for (Node* node = runStart; node && node != pastEndNode; node = NodeTraversal::next(*node)) { + if (node->hasChildNodes()) continue; // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified if (!style->styleIsPresentInComputedStyleOfNode(node)) return true; - if (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName())) + if (m_styledInlineElement && !enclosingElementWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName())) return true; } return false; @@ -862,20 +870,21 @@ void ApplyStyleCommand::removeConflictingInlineStyleFromRun(EditingStyle* style, { ASSERT(runStart && runEnd); RefPtr<Node> next = runStart; - for (RefPtr<Node> node = next; node && node->inDocument() && node != pastEndNode; node = next) { - if (editingIgnoresContent(node.get())) { + for (RefPtr<Node> node = next; node && node->isConnected() && node != pastEndNode; node = next) { + if (editingIgnoresContent(*node)) { ASSERT(!node->contains(pastEndNode.get())); - next = NodeTraversal::nextSkippingChildren(node.get()); + next = NodeTraversal::nextSkippingChildren(*node); } else - next = NodeTraversal::next(node.get()); - if (!node->isHTMLElement()) + next = NodeTraversal::next(*node); + + if (!is<HTMLElement>(*node)) continue; RefPtr<Node> previousSibling = node->previousSibling(); RefPtr<Node> nextSibling = node->nextSibling(); RefPtr<ContainerNode> parent = node->parentNode(); - removeInlineStyleFromElement(style, toHTMLElement(node.get()), RemoveAlways); - if (!node->inDocument()) { + removeInlineStyleFromElement(style, downcast<HTMLElement>(node.get()), RemoveAlways); + if (!node->isConnected()) { // FIXME: We might need to update the start and the end of current selection here but need a test. if (runStart == node) runStart = previousSibling ? previousSibling->nextSibling() : parent->firstChild(); @@ -889,7 +898,7 @@ bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRe { ASSERT(element); - if (!element->parentNode() || !element->parentNode()->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) + if (!element->parentNode() || !isEditableNode(*element->parentNode())) return false; if (isStyledInlineElementToRemove(element.get())) { @@ -905,7 +914,7 @@ bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRe if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle)) removed = true; - if (!element->inDocument()) + if (!element->isConnected()) return removed; // If the node was converted to a span, the span may still contain relevant @@ -922,7 +931,7 @@ void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& removeNodePreservingChildren(elem); else { HTMLElement* newSpanElement = replaceElementWithSpanPreservingChildrenAndAttributes(elem); - ASSERT(newSpanElement && newSpanElement->inDocument()); + ASSERT(newSpanElement && newSpanElement->isConnected()); elem = newSpanElement; } } @@ -947,8 +956,8 @@ bool ApplyStyleCommand::removeImplicitlyStyledElement(EditingStyle* style, HTMLE extractedStyle, attributes, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle)) return false; - for (size_t i = 0; i < attributes.size(); i++) - removeNodeAttribute(element, attributes[i]); + for (auto& attribute : attributes) + removeNodeAttribute(element, attribute); if (isEmptyFontTag(element) || isSpanWithoutAttributesOrUnstyledStyleSpan(element)) removeNodePreservingChildren(element); @@ -964,17 +973,14 @@ bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element if (mode == RemoveNone) return style->conflictsWithInlineStyleOfElement(element); - Vector<CSSPropertyID> properties; - if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, properties)) + RefPtr<MutableStyleProperties> newInlineStyle; + if (!style->conflictsWithInlineStyleOfElement(element, newInlineStyle, extractedStyle)) return false; - // FIXME: We should use a mass-removal function here but we don't have an undoable one yet. - for (size_t i = 0; i < properties.size(); i++) - removeCSSProperty(element, properties[i]); - - // No need to serialize <foo style=""> if we just removed the last css property - if (element->inlineStyle()->isEmpty()) + if (newInlineStyle->isEmpty()) removeNodeAttribute(element, styleAttr); + else + setNodeAttribute(element, styleAttr, newInlineStyle->asText()); if (isSpanWithoutAttributesOrUnstyledStyleSpan(element)) removeNodePreservingChildren(element); @@ -985,17 +991,17 @@ bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(EditingStyle* style, Node* node) { if (!node) - return 0; + return nullptr; - HTMLElement* result = 0; + HTMLElement* result = nullptr; Node* unsplittableElement = unsplittableElementForPosition(firstPositionInOrBeforeNode(node)); - for (Node *n = node; n; n = n->parentNode()) { - if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, toHTMLElement(n))) - result = toHTMLElement(n); + for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) { + if (is<HTMLElement>(*ancestor) && shouldRemoveInlineStyleFromElement(style, downcast<HTMLElement>(ancestor))) + result = downcast<HTMLElement>(ancestor); // Should stop at the editable root (cannot cross editing boundary) and // also stop at the unsplittable element to be consistent with other UAs - if (n == unsplittableElement) + if (ancestor == unsplittableElement) break; } @@ -1008,19 +1014,19 @@ void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, EditingStyle* sty node->document().updateStyleIfNeeded(); - if (!style || style->isEmpty() || !node->renderer() || node->hasTagName(iframeTag)) + if (!style || style->isEmpty() || !node->renderer() || is<HTMLIFrameElement>(*node)) return; RefPtr<EditingStyle> newInlineStyle = style; - if (node->isHTMLElement() && toHTMLElement(node)->inlineStyle()) { + if (is<HTMLElement>(*node) && downcast<HTMLElement>(node)->inlineStyle()) { newInlineStyle = style->copy(); - newInlineStyle->mergeInlineStyleOfElement(toHTMLElement(node), EditingStyle::OverrideValues); + newInlineStyle->mergeInlineStyleOfElement(downcast<HTMLElement>(node), EditingStyle::OverrideValues); } // Since addInlineStyleIfNeeded can't add styles to block-flow render objects, add style attribute instead. // FIXME: applyInlineStyleToRange should be used here instead. - if ((node->renderer()->isRenderBlockFlow() || node->childNodeCount()) && node->isHTMLElement()) { - setNodeAttribute(toHTMLElement(node), styleAttr, newInlineStyle->style()->asText()); + if ((node->renderer()->isRenderBlockFlow() || node->hasChildNodes()) && is<HTMLElement>(*node)) { + setNodeAttribute(downcast<HTMLElement>(node), styleAttr, newInlineStyle->style()->asText()); return; } @@ -1051,31 +1057,31 @@ void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* getChildNodes(*current.get(), currentChildren); RefPtr<StyledElement> styledElement; - if (current->isStyledElement() && isStyledInlineElementToRemove(toElement(current.get()))) { - styledElement = toStyledElement(current.get()); + if (is<StyledElement>(*current) && isStyledInlineElementToRemove(downcast<Element>(current.get()))) { + styledElement = downcast<StyledElement>(current.get()); elementsToPushDown.append(*styledElement); } RefPtr<EditingStyle> styleToPushDown = EditingStyle::create(); - if (current->isHTMLElement()) - removeInlineStyleFromElement(style, toHTMLElement(current.get()), RemoveIfNeeded, styleToPushDown.get()); + if (is<HTMLElement>(*current)) + removeInlineStyleFromElement(style, downcast<HTMLElement>(current.get()), RemoveIfNeeded, styleToPushDown.get()); // The inner loop will go through children on each level // FIXME: we should aggregate inline child elements together so that we don't wrap each child separately. - for (size_t i = 0; i < currentChildren.size(); ++i) { - Node& child = currentChildren[i].get(); + for (Ref<Node>& childRef : currentChildren) { + Node& child = childRef; if (!child.parentNode()) continue; if (!child.contains(targetNode) && elementsToPushDown.size()) { - for (size_t i = 0; i < elementsToPushDown.size(); i++) { - RefPtr<Element> wrapper = elementsToPushDown[i]->cloneElementWithoutChildren(); + for (auto& element : elementsToPushDown) { + RefPtr<Element> wrapper = element->cloneElementWithoutChildren(document()); wrapper->removeAttribute(styleAttr); surroundNodeRangeWithElement(&child, &child, wrapper); } } // Apply style to all nodes containing targetNode and their siblings but NOT to targetNode - // But if we've removed styledElement then go ahead and always apply the style. + // But if we've removed styledElement then always apply the style. if (&child != targetNode || styledElement) applyInlineStyleToPushDown(&child, styleToPushDown.get()); @@ -1087,12 +1093,12 @@ void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* } } -void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &start, const Position &end) +void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position& start, const Position& end) { ASSERT(start.isNotNull()); ASSERT(end.isNotNull()); - ASSERT(start.anchorNode()->inDocument()); - ASSERT(end.anchorNode()->inDocument()); + ASSERT(start.anchorNode()->isConnected()); + ASSERT(end.anchorNode()->isConnected()); ASSERT(comparePositions(start, end) <= 0); // FIXME: We should assert that start/end are not in the middle of a text node. @@ -1100,15 +1106,14 @@ void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &s // If the pushDownStart is at the end of a text node, then this node is not fully selected. // Move it to the next deep quivalent position to avoid removing the style from this node. // e.g. if pushDownStart was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead. - Node* pushDownStartContainer = pushDownStart.containerNode(); - if (pushDownStartContainer && pushDownStartContainer->isTextNode() - && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset()) + auto* pushDownStartContainer = pushDownStart.containerNode(); + if (is<Text>(pushDownStartContainer) && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset()) pushDownStart = nextVisuallyDistinctCandidate(pushDownStart); // If pushDownEnd is at the start of a text node, then this node is not fully selected. // Move it to the previous deep equivalent position to avoid removing the style from this node. Position pushDownEnd = end.upstream(); - Node* pushDownEndContainer = pushDownEnd.containerNode(); - if (pushDownEndContainer && pushDownEndContainer->isTextNode() && !pushDownEnd.computeOffsetInContainerNode()) + auto* pushDownEndContainer = pushDownEnd.containerNode(); + if (is<Text>(pushDownEndContainer) && !pushDownEnd.computeOffsetInContainerNode()) pushDownEnd = previousVisuallyDistinctCandidate(pushDownEnd); pushDownInlineStyleAroundNode(style, pushDownStart.deprecatedNode()); @@ -1125,32 +1130,32 @@ void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &s RefPtr<Node> node = start.deprecatedNode(); while (node) { RefPtr<Node> next; - if (editingIgnoresContent(node.get())) { + if (editingIgnoresContent(*node)) { ASSERT(node == end.deprecatedNode() || !node->contains(end.deprecatedNode())); - next = NodeTraversal::nextSkippingChildren(node.get()); + next = NodeTraversal::nextSkippingChildren(*node); } else - next = NodeTraversal::next(node.get()); + next = NodeTraversal::next(*node); - if (node->isHTMLElement() && nodeFullySelected(node.get(), start, end)) { - RefPtr<HTMLElement> elem = toHTMLElement(node.get()); - RefPtr<Node> prev = NodeTraversal::previousPostOrder(elem.get()); - RefPtr<Node> next = NodeTraversal::next(elem.get()); + if (is<HTMLElement>(*node) && nodeFullySelected(downcast<HTMLElement>(*node), start, end)) { + Ref<HTMLElement> element = downcast<HTMLElement>(*node); + RefPtr<Node> prev = NodeTraversal::previousPostOrder(element); + RefPtr<Node> next = NodeTraversal::next(element); RefPtr<EditingStyle> styleToPushDown; RefPtr<Node> childNode; - if (isStyledInlineElementToRemove(elem.get())) { + if (isStyledInlineElementToRemove(element.ptr())) { styleToPushDown = EditingStyle::create(); - childNode = elem->firstChild(); + childNode = element->firstChild(); } - removeInlineStyleFromElement(style, elem.get(), RemoveIfNeeded, styleToPushDown.get()); - if (!elem->inDocument()) { - if (s.deprecatedNode() == elem) { + removeInlineStyleFromElement(style, element.ptr(), RemoveIfNeeded, styleToPushDown.get()); + if (!element->isConnected()) { + if (s.deprecatedNode() == element.ptr()) { // Since elem must have been fully selected, and it is at the start // of the selection, it is clear we can set the new s offset to 0. ASSERT(s.anchorType() == Position::PositionIsBeforeAnchor || s.offsetInContainerNode() <= 0); s = firstPositionInOrBeforeNode(next.get()); } - if (e.deprecatedNode() == elem) { + if (e.deprecatedNode() == element.ptr()) { // 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. @@ -1172,32 +1177,27 @@ void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &s updateStartEnd(s, e); } -bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const +bool ApplyStyleCommand::nodeFullySelected(Element& element, const Position& start, const Position& end) const { - ASSERT(node); - ASSERT(node->isElementNode()); - // The tree may have changed and Position::upstream() relies on an up-to-date layout. - node->document().updateLayoutIgnorePendingStylesheets(); + element.document().updateLayoutIgnorePendingStylesheets(); - return comparePositions(firstPositionInOrBeforeNode(node), start) >= 0 - && comparePositions(lastPositionInOrAfterNode(node).upstream(), end) <= 0; + return comparePositions(firstPositionInOrBeforeNode(&element), start) >= 0 + && comparePositions(lastPositionInOrAfterNode(&element).upstream(), end) <= 0; } -bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, const Position &end) const +bool ApplyStyleCommand::nodeFullyUnselected(Element& element, const Position& start, const Position& end) const { - ASSERT(node); - ASSERT(node->isElementNode()); - - bool isFullyBeforeStart = comparePositions(lastPositionInOrAfterNode(node).upstream(), start) < 0; - bool isFullyAfterEnd = comparePositions(firstPositionInOrBeforeNode(node), end) > 0; + // The tree may have changed and Position::upstream() relies on an up-to-date layout. + element.document().updateLayoutIgnorePendingStylesheets(); - return isFullyBeforeStart || isFullyAfterEnd; + return comparePositions(lastPositionInOrAfterNode(&element).upstream(), start) < 0 + || comparePositions(firstPositionInOrBeforeNode(&element), end) > 0; } void ApplyStyleCommand::splitTextAtStart(const Position& start, const Position& end) { - ASSERT(start.containerNode()->isTextNode()); + ASSERT(is<Text>(start.containerNode())); Position newEnd; if (end.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode()) @@ -1212,23 +1212,23 @@ void ApplyStyleCommand::splitTextAtStart(const Position& start, const Position& void ApplyStyleCommand::splitTextAtEnd(const Position& start, const Position& end) { - ASSERT(end.containerNode()->isTextNode()); + ASSERT(is<Text>(end.containerNode())); bool shouldUpdateStart = start.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode(); - Text* text = toText(end.deprecatedNode()); - splitTextNode(text, end.offsetInContainerNode()); + Text& text = downcast<Text>(*end.deprecatedNode()); + splitTextNode(&text, end.offsetInContainerNode()); - Node* prevNode = text->previousSibling(); - if (!prevNode || !prevNode->isTextNode()) + Node* prevNode = text.previousSibling(); + if (!is<Text>(prevNode)) return; - Position newStart = shouldUpdateStart ? Position(toText(prevNode), start.offsetInContainerNode()) : start; + Position newStart = shouldUpdateStart ? Position(downcast<Text>(prevNode), start.offsetInContainerNode()) : start; updateStartEnd(newStart, lastPositionInNode(prevNode)); } void ApplyStyleCommand::splitTextElementAtStart(const Position& start, const Position& end) { - ASSERT(start.containerNode()->isTextNode()); + ASSERT(is<Text>(start.containerNode())); Position newEnd; if (start.containerNode() == end.containerNode()) @@ -1242,7 +1242,7 @@ void ApplyStyleCommand::splitTextElementAtStart(const Position& start, const Pos void ApplyStyleCommand::splitTextElementAtEnd(const Position& start, const Position& end) { - ASSERT(end.containerNode()->isTextNode()); + ASSERT(is<Text>(end.containerNode())); bool shouldUpdateStart = start.containerNode() == end.containerNode(); splitTextNodeContainingElement(end.containerText(), end.offsetInContainerNode()); @@ -1251,33 +1251,33 @@ void ApplyStyleCommand::splitTextElementAtEnd(const Position& start, const Posit if (!parentElement || !parentElement->previousSibling()) return; Node* firstTextNode = parentElement->previousSibling()->lastChild(); - if (!firstTextNode || !firstTextNode->isTextNode()) + if (!is<Text>(firstTextNode)) return; - Position newStart = shouldUpdateStart ? Position(toText(firstTextNode), start.offsetInContainerNode()) : start; + Position newStart = shouldUpdateStart ? Position(downcast<Text>(firstTextNode), start.offsetInContainerNode()) : start; updateStartEnd(newStart, positionAfterNode(firstTextNode)); } bool ApplyStyleCommand::shouldSplitTextElement(Element* element, EditingStyle* style) { - if (!element || !element->isHTMLElement()) + if (!is<HTMLElement>(element)) return false; - return shouldRemoveInlineStyleFromElement(style, toHTMLElement(element)); + return shouldRemoveInlineStyleFromElement(style, &downcast<HTMLElement>(*element)); } bool ApplyStyleCommand::isValidCaretPositionInTextNode(const Position& position) { Node* node = position.containerNode(); - if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node->isTextNode()) + if (position.anchorType() != Position::PositionIsOffsetInAnchor || !is<Text>(node)) return false; int offsetInText = position.offsetInContainerNode(); - return offsetInText > caretMinOffset(node) && offsetInText < caretMaxOffset(node); + return offsetInText > caretMinOffset(*node) && offsetInText < caretMaxOffset(*node); } bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position& start, const Position& end) { - Node* startNode = start.containerNode(); + auto* startNode = start.containerNode(); int startOffset = start.computeOffsetInContainerNode(); if (startOffset) return false; @@ -1289,29 +1289,23 @@ bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position& start, return false; startNode = startNode->parentNode(); - startOffset = 0; } - if (!startNode->isElementNode()) + auto* previousSibling = startNode->previousSibling(); + if (!previousSibling || !areIdenticalElements(*startNode, *previousSibling)) return false; - Node* previousSibling = startNode->previousSibling(); - - if (previousSibling && areIdenticalElements(startNode, previousSibling)) { - Element* previousElement = toElement(previousSibling); - Element* element = toElement(startNode); - Node* startChild = element->firstChild(); - ASSERT(startChild); - mergeIdenticalElements(previousElement, element); + auto& previousElement = downcast<Element>(*previousSibling); + auto& element = downcast<Element>(*startNode); + auto* startChild = element.firstChild(); + ASSERT(startChild); + mergeIdenticalElements(&previousElement, &element); - int startOffsetAdjustment = startChild->nodeIndex(); - int endOffsetAdjustment = startNode == end.deprecatedNode() ? startOffsetAdjustment : 0; - updateStartEnd(Position(startNode, startOffsetAdjustment, Position::PositionIsOffsetInAnchor), - Position(end.deprecatedNode(), end.deprecatedEditingOffset() + endOffsetAdjustment, Position::PositionIsOffsetInAnchor)); - return true; - } - - return false; + int startOffsetAdjustment = startChild->computeNodeIndex(); + int endOffsetAdjustment = startNode == end.deprecatedNode() ? startOffsetAdjustment : 0; + updateStartEnd({ startNode, startOffsetAdjustment, Position::PositionIsOffsetInAnchor}, + { end.deprecatedNode(), end.deprecatedEditingOffset() + endOffsetAdjustment, Position::PositionIsOffsetInAnchor }); + return true; } bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position& start, const Position& end) @@ -1326,25 +1320,24 @@ bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position& start, const endNode = end.deprecatedNode()->parentNode(); } - if (!endNode->isElementNode() || endNode->hasTagName(brTag)) + if (endNode->hasTagName(brTag)) return false; Node* nextSibling = endNode->nextSibling(); - if (nextSibling && areIdenticalElements(endNode, nextSibling)) { - Element* nextElement = toElement(nextSibling); - Element* element = toElement(endNode); - Node* nextChild = nextElement->firstChild(); + if (!nextSibling || !areIdenticalElements(*endNode, *nextSibling)) + return false; - mergeIdenticalElements(element, nextElement); + auto& nextElement = downcast<Element>(*nextSibling); + auto& element = downcast<Element>(*endNode); + Node* nextChild = nextElement.firstChild(); - bool shouldUpdateStart = start.containerNode() == endNode; - int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodeCount(); - updateStartEnd(shouldUpdateStart ? Position(nextElement, start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor) : start, - Position(nextElement, endOffset, Position::PositionIsOffsetInAnchor)); - return true; - } + mergeIdenticalElements(&element, &nextElement); - return false; + bool shouldUpdateStart = start.containerNode() == endNode; + int endOffset = nextChild ? nextChild->computeNodeIndex() : nextElement.countChildNodes(); + updateStartEnd(shouldUpdateStart ? Position(&nextElement, start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor) : start, + { &nextElement, endOffset, Position::PositionIsOffsetInAnchor }); + return true; } void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStartNode, PassRefPtr<Node> endNode, PassRefPtr<Element> elementToInsert) @@ -1360,7 +1353,7 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStar RefPtr<Node> node = startNode; while (node) { RefPtr<Node> next = node->nextSibling(); - if (node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) { + if (isEditableNode(*node)) { removeNode(node); appendNode(node, element); } @@ -1371,15 +1364,15 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStar RefPtr<Node> nextSibling = element->nextSibling(); RefPtr<Node> previousSibling = element->previousSibling(); - if (nextSibling && nextSibling->isElementNode() && nextSibling->hasEditableStyle() - && areIdenticalElements(element.get(), toElement(nextSibling.get()))) - mergeIdenticalElements(element.get(), toElement(nextSibling.get())); - - if (previousSibling && previousSibling->isElementNode() && previousSibling->hasEditableStyle()) { - Node* mergedElement = previousSibling->nextSibling(); - if (mergedElement->isElementNode() && mergedElement->hasEditableStyle() - && areIdenticalElements(toElement(previousSibling.get()), toElement(mergedElement))) - mergeIdenticalElements(toElement(previousSibling.get()), toElement(mergedElement)); + + if (nextSibling && nextSibling->hasEditableStyle() && areIdenticalElements(*element, *nextSibling)) + mergeIdenticalElements(element.get(), downcast<Element>(nextSibling.get())); + + if (is<Element>(previousSibling.get()) && previousSibling->hasEditableStyle()) { + auto* mergedElement = previousSibling->nextSibling(); + ASSERT(mergedElement); + if (mergedElement->hasEditableStyle() && areIdenticalElements(*previousSibling, *mergedElement)) + mergeIdenticalElements(downcast<Element>(previousSibling.get()), downcast<Element>(mergedElement)); } // FIXME: We should probably call updateStartEnd if the start or end was in the node @@ -1389,12 +1382,13 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStar void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block) { + ASSERT(styleChange.cssStyle()); // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for // inline content. if (!block) return; - String cssStyle = styleChange.cssStyle(); + String cssStyle = styleChange.cssStyle()->asText(); StringBuilder cssText; cssText.append(cssStyle); if (const StyleProperties* decl = block->inlineStyle()) { @@ -1407,7 +1401,7 @@ void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElemen void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, EAddStyledElement addStyledElement) { - if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd->inDocument()) + if (!passedStart || !passedEnd || !passedStart->isConnected() || !passedEnd->isConnected()) return; RefPtr<Node> start = passedStart; @@ -1423,7 +1417,7 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtr<N Position ApplyStyleCommand::positionToComputeInlineStyleChange(PassRefPtr<Node> startNode, RefPtr<Node>& dummyElement) { // It's okay to obtain the style at the startNode because we've removed all relevant styles from the current run. - if (!startNode->isElementNode()) { + if (!is<Element>(*startNode)) { dummyElement = createStyleSpanElement(document()); insertNodeAt(dummyElement, positionBeforeNode(startNode.get())); return firstPositionInOrBeforeNode(dummyElement.get()); @@ -1436,22 +1430,25 @@ void ApplyStyleCommand::applyInlineStyleChange(PassRefPtr<Node> passedStart, Pas { RefPtr<Node> startNode = passedStart; RefPtr<Node> endNode = passedEnd; - ASSERT(startNode->inDocument()); - ASSERT(endNode->inDocument()); + ASSERT(startNode->isConnected()); + ASSERT(endNode->isConnected()); // Find appropriate font and span elements top-down. - HTMLElement* fontContainer = 0; - HTMLElement* styleContainer = 0; - for (Node* container = startNode.get(); container && startNode == endNode; container = container->firstChild()) { - if (container->isHTMLElement() && container->hasTagName(fontTag)) - fontContainer = toHTMLElement(container); - bool styleContainerIsNotSpan = !styleContainer || !styleContainer->hasTagName(spanTag); - if (container->isHTMLElement() && (container->hasTagName(spanTag) || (styleContainerIsNotSpan && container->childNodeCount()))) - styleContainer = toHTMLElement(container); - if (!container->firstChild()) + HTMLFontElement* fontContainer = nullptr; + HTMLElement* styleContainer = nullptr; + while (startNode == endNode) { + if (is<HTMLElement>(*startNode)) { + auto& container = downcast<HTMLElement>(*startNode); + if (is<HTMLFontElement>(container)) + fontContainer = &downcast<HTMLFontElement>(container); + if (is<HTMLSpanElement>(container) || (!is<HTMLSpanElement>(styleContainer) && container.hasChildNodes())) + styleContainer = &container; + } + auto* startNodeFirstChild = startNode->firstChild(); + if (!startNodeFirstChild) break; - startNode = container->firstChild(); - endNode = container->lastChild(); + endNode = startNode->lastChild(); + startNode = startNodeFirstChild; } // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes. @@ -1464,33 +1461,29 @@ void ApplyStyleCommand::applyInlineStyleChange(PassRefPtr<Node> passedStart, Pas if (styleChange.applyFontSize()) setNodeAttribute(fontContainer, sizeAttr, styleChange.fontSize()); } else { - RefPtr<Element> fontElement = createFontElement(document()); + auto fontElement = createFontElement(document()); if (styleChange.applyFontColor()) - fontElement->setAttribute(colorAttr, styleChange.fontColor()); + fontElement->setAttributeWithoutSynchronization(colorAttr, styleChange.fontColor()); if (styleChange.applyFontFace()) - fontElement->setAttribute(faceAttr, styleChange.fontFace()); + fontElement->setAttributeWithoutSynchronization(faceAttr, styleChange.fontFace()); if (styleChange.applyFontSize()) - fontElement->setAttribute(sizeAttr, styleChange.fontSize()); + fontElement->setAttributeWithoutSynchronization(sizeAttr, styleChange.fontSize()); surroundNodeRangeWithElement(startNode, endNode, fontElement.get()); } } - if (styleChange.cssStyle().length()) { + if (auto styleToMerge = styleChange.cssStyle()) { if (styleContainer) { - if (const StyleProperties* existingStyle = styleContainer->inlineStyle()) { - String existingText = existingStyle->asText(); - StringBuilder cssText; - cssText.append(existingText); - if (!existingText.isEmpty()) - cssText.append(' '); - cssText.append(styleChange.cssStyle()); - setNodeAttribute(styleContainer, styleAttr, cssText.toString()); + if (auto existingStyle = styleContainer->inlineStyle()) { + auto inlineStyle = EditingStyle::create(existingStyle); + inlineStyle->overrideWithStyle(styleToMerge); + setNodeAttribute(styleContainer, styleAttr, inlineStyle->style()->asText()); } else - setNodeAttribute(styleContainer, styleAttr, styleChange.cssStyle()); + setNodeAttribute(styleContainer, styleAttr, styleToMerge->asText()); } else { - RefPtr<Element> styleElement = createStyleSpanElement(document()); - styleElement->setAttribute(styleAttr, styleChange.cssStyle()); - surroundNodeRangeWithElement(startNode, endNode, styleElement.release()); + auto styleElement = createStyleSpanElement(document()); + styleElement->setAttribute(styleAttr, styleToMerge->asText()); + surroundNodeRangeWithElement(startNode, endNode, WTFMove(styleElement)); } } @@ -1512,7 +1505,7 @@ void ApplyStyleCommand::applyInlineStyleChange(PassRefPtr<Node> passedStart, Pas surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag)); if (m_styledInlineElement && addStyledElement == AddStyledElement) - surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren()); + surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren(document())); } float ApplyStyleCommand::computedFontSize(Node* node) @@ -1520,9 +1513,8 @@ float ApplyStyleCommand::computedFontSize(Node* node) if (!node) return 0; - RefPtr<CSSValue> value = ComputedStyleExtractor(node).propertyValue(CSSPropertyFontSize); - ASSERT(value && value->isPrimitiveValue()); - return toCSSPrimitiveValue(value.get())->getFloatValue(CSSPrimitiveValue::CSS_PX); + auto value = ComputedStyleExtractor(node).propertyValue(CSSPropertyFontSize); + return downcast<CSSPrimitiveValue>(*value).floatValue(CSSPrimitiveValue::CSS_PX); } void ApplyStyleCommand::joinChildTextNodes(Node* node, const Position& start, const Position& end) @@ -1534,21 +1526,20 @@ void ApplyStyleCommand::joinChildTextNodes(Node* node, const Position& start, co Position newEnd = end; Vector<RefPtr<Text>> textNodes; - for (Text* textNode = TextNodeTraversal::firstChild(node); textNode; textNode = TextNodeTraversal::nextSibling(textNode)) + for (Text* textNode = TextNodeTraversal::firstChild(*node); textNode; textNode = TextNodeTraversal::nextSibling(*textNode)) textNodes.append(textNode); - for (size_t i = 0; i < textNodes.size(); ++i) { - Text* childText = textNodes[i].get(); + for (auto& childText : textNodes) { Node* next = childText->nextSibling(); - if (!next || !next->isTextNode()) + if (!is<Text>(next)) continue; - Text* nextText = toText(next); + Text& nextText = downcast<Text>(*next); if (start.anchorType() == Position::PositionIsOffsetInAnchor && next == start.containerNode()) - newStart = Position(childText, childText->length() + start.offsetInContainerNode()); + newStart = Position(childText.get(), childText->length() + start.offsetInContainerNode()); if (end.anchorType() == Position::PositionIsOffsetInAnchor && next == end.containerNode()) - newEnd = Position(childText, childText->length() + end.offsetInContainerNode()); - String textToMove = nextText->data(); + newEnd = Position(childText.get(), childText->length() + end.offsetInContainerNode()); + String textToMove = nextText.data(); insertTextIntoNode(childText, childText->length(), textToMove); removeNode(next); // don't move child node pointer. it may want to merge with more text nodes. diff --git a/Source/WebCore/editing/ApplyStyleCommand.h b/Source/WebCore/editing/ApplyStyleCommand.h index cda88c81e..a485498f9 100644 --- a/Source/WebCore/editing/ApplyStyleCommand.h +++ b/Source/WebCore/editing/ApplyStyleCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ApplyStyleCommand_h -#define ApplyStyleCommand_h +#pragma once #include "CompositeEditCommand.h" #include "HTMLElement.h" @@ -48,21 +47,21 @@ public: enum EAddStyledElement { AddStyledElement, DoNotAddStyledElement }; typedef bool (*IsInlineElementToRemoveFunction)(const Element*); - static PassRefPtr<ApplyStyleCommand> create(Document& document, const EditingStyle* style, EditAction action = EditActionChangeAttributes, EPropertyLevel level = PropertyDefault) + static Ref<ApplyStyleCommand> create(Document& document, const EditingStyle* style, EditAction action = EditActionChangeAttributes, EPropertyLevel level = PropertyDefault) { - return adoptRef(new ApplyStyleCommand(document, style, action, level)); + return adoptRef(*new ApplyStyleCommand(document, style, action, level)); } - static PassRefPtr<ApplyStyleCommand> create(Document& document, const EditingStyle* style, const Position& start, const Position& end, EditAction action = EditActionChangeAttributes, EPropertyLevel level = PropertyDefault) + static Ref<ApplyStyleCommand> create(Document& document, const EditingStyle* style, const Position& start, const Position& end, EditAction action = EditActionChangeAttributes, EPropertyLevel level = PropertyDefault) { - return adoptRef(new ApplyStyleCommand(document, style, start, end, action, level)); + return adoptRef(*new ApplyStyleCommand(document, style, start, end, action, level)); } - static PassRefPtr<ApplyStyleCommand> create(PassRefPtr<Element> element, bool removeOnly = false, EditAction action = EditActionChangeAttributes) + static Ref<ApplyStyleCommand> create(PassRefPtr<Element> element, bool removeOnly = false, EditAction action = EditActionChangeAttributes) { - return adoptRef(new ApplyStyleCommand(element, removeOnly, action)); + return adoptRef(*new ApplyStyleCommand(element, removeOnly, action)); } - static PassRefPtr<ApplyStyleCommand> create(Document& document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction action = EditActionChangeAttributes) + static Ref<ApplyStyleCommand> create(Document& document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction action = EditActionChangeAttributes) { - return adoptRef(new ApplyStyleCommand(document, style, isInlineElementToRemoveFunction, action)); + return adoptRef(*new ApplyStyleCommand(document, style, isInlineElementToRemoveFunction, action)); } private: @@ -71,24 +70,24 @@ private: ApplyStyleCommand(PassRefPtr<Element>, bool removeOnly, EditAction); ApplyStyleCommand(Document&, const EditingStyle*, bool (*isInlineElementToRemove)(const Element*), EditAction); - virtual void doApply() override; - virtual EditAction editingAction() const override; + void doApply() override; + bool shouldDispatchInputEvents() const final { return false; } // style-removal helpers bool isStyledInlineElementToRemove(Element*) const; bool shouldApplyInlineStyleToRun(EditingStyle*, Node* runStart, Node* pastEndNode); void removeConflictingInlineStyleFromRun(EditingStyle*, RefPtr<Node>& runStart, RefPtr<Node>& runEnd, PassRefPtr<Node> pastEndNode); - bool removeInlineStyleFromElement(EditingStyle*, PassRefPtr<HTMLElement>, InlineStyleRemovalMode = RemoveIfNeeded, EditingStyle* extractedStyle = 0); + bool removeInlineStyleFromElement(EditingStyle*, PassRefPtr<HTMLElement>, InlineStyleRemovalMode = RemoveIfNeeded, EditingStyle* extractedStyle = nullptr); inline bool shouldRemoveInlineStyleFromElement(EditingStyle* style, HTMLElement* element) {return removeInlineStyleFromElement(style, element, RemoveNone);} void replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*&); bool removeImplicitlyStyledElement(EditingStyle*, HTMLElement*, InlineStyleRemovalMode, EditingStyle* extractedStyle); - bool removeCSSStyle(EditingStyle*, HTMLElement*, InlineStyleRemovalMode = RemoveIfNeeded, EditingStyle* extractedStyle = 0); + bool removeCSSStyle(EditingStyle*, HTMLElement*, InlineStyleRemovalMode = RemoveIfNeeded, EditingStyle* extractedStyle = nullptr); HTMLElement* highestAncestorWithConflictingInlineStyle(EditingStyle*, Node*); void applyInlineStyleToPushDown(Node*, EditingStyle*); void pushDownInlineStyleAroundNode(EditingStyle*, Node*); void removeInlineStyle(EditingStyle* , const Position& start, const Position& end); - bool nodeFullySelected(Node*, const Position& start, const Position& end) const; - bool nodeFullyUnselected(Node*, const Position& start, const Position& end) const; + bool nodeFullySelected(Element&, const Position& start, const Position& end) const; + bool nodeFullyUnselected(Element&, const Position& start, const Position& end) const; // style-application helpers void applyBlockStyle(EditingStyle*); @@ -122,22 +121,19 @@ private: Position endPosition(); RefPtr<EditingStyle> m_style; - EditAction m_editingAction; EPropertyLevel m_propertyLevel; Position m_start; Position m_end; bool m_useEndingSelection; RefPtr<Element> m_styledInlineElement; bool m_removeOnly; - IsInlineElementToRemoveFunction m_isInlineElementToRemoveFunction; + IsInlineElementToRemoveFunction m_isInlineElementToRemoveFunction { nullptr }; }; enum ShouldStyleAttributeBeEmpty { AllowNonEmptyStyleAttribute, StyleAttributeShouldBeEmpty }; bool isEmptyFontTag(const Element*, ShouldStyleAttributeBeEmpty = StyleAttributeShouldBeEmpty); bool isLegacyAppleStyleSpan(const Node*); bool isStyleSpanOrSpanWithOnlyStyleAttribute(const Element*); -PassRefPtr<HTMLElement> createStyleSpanElement(Document&); +RefPtr<HTMLElement> createStyleSpanElement(Document&); } // namespace WebCore - -#endif diff --git a/Source/WebCore/editing/BreakBlockquoteCommand.cpp b/Source/WebCore/editing/BreakBlockquoteCommand.cpp index dde7fcb93..f1af20efb 100644 --- a/Source/WebCore/editing/BreakBlockquoteCommand.cpp +++ b/Source/WebCore/editing/BreakBlockquoteCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2005 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -26,7 +26,7 @@ #include "config.h" #include "BreakBlockquoteCommand.h" -#include "HTMLElement.h" +#include "HTMLBRElement.h" #include "HTMLNames.h" #include "NodeTraversal.h" #include "RenderListItem.h" @@ -70,25 +70,25 @@ void BreakBlockquoteCommand::doApply() if (!topBlockquote || !topBlockquote->parentNode() || !topBlockquote->isElementNode()) return; - RefPtr<Element> breakNode = createBreakElement(document()); + auto breakNode = HTMLBRElement::create(document()); bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote); // If the position is at the beginning of the top quoted content, we don't need to break the quote. // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content. if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) { - insertNodeBefore(breakNode.get(), topBlockquote); - setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); + insertNodeBefore(breakNode.copyRef(), topBlockquote); + setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.ptr()), DOWNSTREAM, endingSelection().isDirectional())); rebalanceWhitespace(); return; } // Insert a break after the top blockquote. - insertNodeAfter(breakNode.get(), topBlockquote); + insertNodeAfter(breakNode.copyRef(), topBlockquote); // If we're inserting the break at the end of the quoted content, we don't need to break the quote. if (isLastVisPosInNode) { - setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); + setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.ptr()), DOWNSTREAM, endingSelection().isDirectional())); rebalanceWhitespace(); return; } @@ -104,23 +104,23 @@ void BreakBlockquoteCommand::doApply() // startNode is the first node that we need to move to the new blockquote. Node* startNode = pos.deprecatedNode(); - + ASSERT(startNode); // Split at pos if in the middle of a text node. - if (startNode->isTextNode()) { - Text* textNode = toText(startNode); - if ((unsigned)pos.deprecatedEditingOffset() >= textNode->length()) { - startNode = NodeTraversal::next(startNode); + if (is<Text>(*startNode)) { + Text& textNode = downcast<Text>(*startNode); + if ((unsigned)pos.deprecatedEditingOffset() >= textNode.length()) { + startNode = NodeTraversal::next(*startNode); ASSERT(startNode); } else if (pos.deprecatedEditingOffset() > 0) - splitTextNode(textNode, pos.deprecatedEditingOffset()); + splitTextNode(&textNode, pos.deprecatedEditingOffset()); } else if (pos.deprecatedEditingOffset() > 0) { - Node* childAtOffset = startNode->childNode(pos.deprecatedEditingOffset()); - startNode = childAtOffset ? childAtOffset : NodeTraversal::next(startNode); + Node* childAtOffset = startNode->traverseToChildAt(pos.deprecatedEditingOffset()); + startNode = childAtOffset ? childAtOffset : NodeTraversal::next(*startNode); ASSERT(startNode); } // If there's nothing inside topBlockquote to move, we're finished. - if (!startNode->isDescendantOf(topBlockquote)) { + if (!startNode->isDescendantOf(*topBlockquote)) { setEndingSelection(VisibleSelection(VisiblePosition(firstPositionInOrBeforeNode(startNode)), endingSelection().isDirectional())); return; } @@ -131,8 +131,8 @@ void BreakBlockquoteCommand::doApply() ancestors.append(node); // Insert a clone of the top blockquote after the break. - RefPtr<Element> clonedBlockquote = toElement(topBlockquote)->cloneElementWithoutChildren(); - insertNodeAfter(clonedBlockquote.get(), breakNode.get()); + RefPtr<Element> clonedBlockquote = downcast<Element>(*topBlockquote).cloneElementWithoutChildren(document()); + insertNodeAfter(clonedBlockquote.get(), breakNode.copyRef()); // Clone startNode's ancestors into the cloned blockquote. // On exiting this loop, clonedAncestor is the lowest ancestor @@ -140,7 +140,7 @@ void BreakBlockquoteCommand::doApply() // or clonedBlockquote if ancestors is empty). RefPtr<Element> clonedAncestor = clonedBlockquote; for (size_t i = ancestors.size(); i != 0; --i) { - RefPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(); + RefPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(document()); // Preserve list item numbering in cloned lists. if (clonedChild->isElementNode() && clonedChild->hasTagName(olTag)) { Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode; @@ -148,8 +148,8 @@ void BreakBlockquoteCommand::doApply() // find the first one so that we know where to start numbering. while (listChildNode && !listChildNode->hasTagName(liTag)) listChildNode = listChildNode->nextSibling(); - if (listChildNode && listChildNode->renderer() && listChildNode->renderer()->isListItem()) - setNodeAttribute(clonedChild, startAttr, AtomicString::number(toRenderListItem(listChildNode->renderer())->value())); + if (listChildNode && is<RenderListItem>(listChildNode->renderer())) + setNodeAttribute(clonedChild, startAttr, AtomicString::number(downcast<RenderListItem>(*listChildNode->renderer()).value())); } appendNode(clonedChild.get(), clonedAncestor.get()); @@ -180,7 +180,7 @@ void BreakBlockquoteCommand::doApply() addBlockPlaceholderIfNeeded(clonedBlockquote.get()); // Put the selection right before the break. - setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); + setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.ptr()), DOWNSTREAM, endingSelection().isDirectional())); rebalanceWhitespace(); } diff --git a/Source/WebCore/editing/BreakBlockquoteCommand.h b/Source/WebCore/editing/BreakBlockquoteCommand.h index 90c2c6b6c..57da07015 100644 --- a/Source/WebCore/editing/BreakBlockquoteCommand.h +++ b/Source/WebCore/editing/BreakBlockquoteCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef BreakBlockquoteCommand_h -#define BreakBlockquoteCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -32,16 +31,14 @@ namespace WebCore { class BreakBlockquoteCommand : public CompositeEditCommand { public: - static PassRefPtr<BreakBlockquoteCommand> create(Document& document) + static Ref<BreakBlockquoteCommand> create(Document& document) { - return adoptRef(new BreakBlockquoteCommand(document)); + return adoptRef(*new BreakBlockquoteCommand(document)); } private: explicit BreakBlockquoteCommand(Document&); - virtual void doApply() override; + void doApply() override; }; } // namespace WebCore - -#endif diff --git a/Source/WebCore/editing/ClipboardAccessPolicy.h b/Source/WebCore/editing/ClipboardAccessPolicy.h new file mode 100644 index 000000000..f1c6c52f2 --- /dev/null +++ b/Source/WebCore/editing/ClipboardAccessPolicy.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 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. AND ITS 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 APPLE INC. OR ITS 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. + */ + +#pragma once + +namespace WebCore { + +enum class ClipboardAccessPolicy { + Allow, + Deny, + RequiresUserGesture +}; + +} // namespace WebCore diff --git a/Source/WebCore/editing/CompositeEditCommand.cpp b/Source/WebCore/editing/CompositeEditCommand.cpp index 9cd2ba987..61ddbf50e 100644 --- a/Source/WebCore/editing/CompositeEditCommand.cpp +++ b/Source/WebCore/editing/CompositeEditCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -26,8 +26,11 @@ #include "config.h" #include "CompositeEditCommand.h" +#include "AXObjectCache.h" #include "AppendNodeCommand.h" #include "ApplyStyleCommand.h" +#include "BreakBlockquoteCommand.h" +#include "DataTransfer.h" #include "DeleteFromTextNodeCommand.h" #include "DeleteSelectionCommand.h" #include "Document.h" @@ -36,10 +39,13 @@ #include "Editor.h" #include "EditorInsertAction.h" #include "ElementTraversal.h" -#include "ExceptionCodePlaceholder.h" +#include "Event.h" #include "Frame.h" -#include "HTMLElement.h" +#include "HTMLBRElement.h" +#include "HTMLDivElement.h" +#include "HTMLLIElement.h" #include "HTMLNames.h" +#include "HTMLSpanElement.h" #include "InlineTextBox.h" #include "InsertIntoTextNodeCommand.h" #include "InsertLineBreakCommand.h" @@ -52,15 +58,17 @@ #include "RemoveCSSPropertyCommand.h" #include "RemoveNodeCommand.h" #include "RemoveNodePreservingChildrenCommand.h" +#include "RenderBlockFlow.h" +#include "RenderText.h" +#include "RenderedDocumentMarker.h" #include "ReplaceNodeWithSpanCommand.h" #include "ReplaceSelectionCommand.h" -#include "RenderBlock.h" -#include "RenderText.h" #include "ScopedEventQueue.h" #include "SetNodeAttributeCommand.h" #include "SplitElementCommand.h" #include "SplitTextNodeCommand.h" #include "SplitTextNodeContainingElementCommand.h" +#include "StaticRange.h" #include "Text.h" #include "TextIterator.h" #include "VisibleUnits.h" @@ -68,22 +76,127 @@ #include "htmlediting.h" #include "markup.h" -#if ENABLE(DELETION_UI) -#include "DeleteButtonController.h" -#endif - -#if PLATFORM(IOS) -#include "BreakBlockquoteCommand.h" -#endif - namespace WebCore { using namespace HTMLNames; -PassRefPtr<EditCommandComposition> EditCommandComposition::create(Document& document, +int AccessibilityUndoReplacedText::indexForVisiblePosition(const VisiblePosition& position, RefPtr<ContainerNode>& scope) const +{ + if (position.deepEquivalent().isNull()) + return -1; + return WebCore::indexForVisiblePosition(position, scope); +} + +void AccessibilityUndoReplacedText::configureRangeDeletedByReapplyWithEndingSelection(const VisibleSelection& selection) +{ + if (!AXObjectCache::accessibilityEnabled()) + return; + if (selection.isNone()) + return; + m_rangeDeletedByReapply.endIndex.value = indexForVisiblePosition(selection.end(), m_rangeDeletedByReapply.endIndex.scope); +} + +void AccessibilityUndoReplacedText::configureRangeDeletedByReapplyWithStartingSelection(const VisibleSelection& selection) +{ + if (!AXObjectCache::accessibilityEnabled()) + return; + if (selection.isNone()) + return; + if (m_rangeDeletedByReapply.startIndex.value == -1) + m_rangeDeletedByReapply.startIndex.value = indexForVisiblePosition(selection.start(), m_rangeDeletedByReapply.startIndex.scope); +} + +void AccessibilityUndoReplacedText::setRangeDeletedByUnapply(const VisiblePositionIndexRange& range) +{ + if (m_rangeDeletedByUnapply.isNull()) + m_rangeDeletedByUnapply = range; +} + +void AccessibilityUndoReplacedText::captureTextForUnapply() +{ + if (!AXObjectCache::accessibilityEnabled()) + return; + m_replacedText = textDeletedByReapply(); +} + +void AccessibilityUndoReplacedText::captureTextForReapply() +{ + if (!AXObjectCache::accessibilityEnabled()) + return; + m_replacedText = textDeletedByUnapply(); +} + +static String stringForVisiblePositionIndexRange(const VisiblePositionIndexRange& range) +{ + if (range.isNull()) + return String(); + VisiblePosition start = visiblePositionForIndex(range.startIndex.value, range.startIndex.scope.get()); + VisiblePosition end = visiblePositionForIndex(range.endIndex.value, range.endIndex.scope.get()); + return AccessibilityObject::stringForVisiblePositionRange(VisiblePositionRange(start, end)); +} + +String AccessibilityUndoReplacedText::textDeletedByUnapply() +{ + if (!AXObjectCache::accessibilityEnabled()) + return String(); + return stringForVisiblePositionIndexRange(m_rangeDeletedByUnapply); +} + +String AccessibilityUndoReplacedText::textDeletedByReapply() +{ + if (!AXObjectCache::accessibilityEnabled()) + return String(); + return stringForVisiblePositionIndexRange(m_rangeDeletedByReapply); +} + +static void postTextStateChangeNotification(AXObjectCache* cache, const VisiblePosition& position, const String& deletedText, const String& insertedText) +{ + ASSERT(cache); + auto* node = highestEditableRoot(position.deepEquivalent(), HasEditableAXRole); + if (!node) + return; + if (insertedText.length() && deletedText.length()) + cache->postTextReplacementNotification(node, AXTextEditTypeDelete, insertedText, AXTextEditTypeInsert, deletedText, position); + else if (deletedText.length()) + cache->postTextStateChangeNotification(node, AXTextEditTypeInsert, deletedText, position); + else if (insertedText.length()) + cache->postTextStateChangeNotification(node, AXTextEditTypeDelete, insertedText, position); +} + +void AccessibilityUndoReplacedText::postTextStateChangeNotificationForUnapply(AXObjectCache* cache) +{ + if (!cache) + return; + if (!AXObjectCache::accessibilityEnabled()) + return; + if (m_rangeDeletedByUnapply.isNull()) + return; + VisiblePosition position = visiblePositionForIndex(m_rangeDeletedByUnapply.endIndex.value, m_rangeDeletedByUnapply.endIndex.scope.get()); + if (position.isNull()) + return; + postTextStateChangeNotification(cache, position, textDeletedByUnapply(), m_replacedText); + m_replacedText = String(); +} + +void AccessibilityUndoReplacedText::postTextStateChangeNotificationForReapply(AXObjectCache* cache) +{ + if (!cache) + return; + if (!AXObjectCache::accessibilityEnabled()) + return; + if (m_rangeDeletedByReapply.isNull()) + return; + VisiblePosition position = visiblePositionForIndex(m_rangeDeletedByReapply.startIndex.value, m_rangeDeletedByReapply.startIndex.scope.get()); + if (position.isNull()) + return; + postTextStateChangeNotification(cache, position, textDeletedByReapply(), m_replacedText); + m_replacedText = String(); +} + +Ref<EditCommandComposition> EditCommandComposition::create(Document& document, const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction) { - return adoptRef(new EditCommandComposition(document, startingSelection, endingSelection, editAction)); + return adoptRef(*new EditCommandComposition(document, startingSelection, endingSelection, editAction)); } EditCommandComposition::EditCommandComposition(Document& document, const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction) @@ -94,13 +207,17 @@ EditCommandComposition::EditCommandComposition(Document& document, const Visible , m_endingRootEditableElement(endingSelection.rootEditableElement()) , m_editAction(editAction) { + m_replacedText.configureRangeDeletedByReapplyWithStartingSelection(startingSelection); } void EditCommandComposition::unapply() { ASSERT(m_document); RefPtr<Frame> frame = m_document->frame(); - ASSERT(frame); + if (!frame) + return; + + m_replacedText.captureTextForUnapply(); // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>. // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one @@ -115,40 +232,43 @@ void EditCommandComposition::unapply() frame->editor().cancelComposition(); #endif - { -#if ENABLE(DELETION_UI) - DeleteButtonControllerDisableScope deleteButtonControllerDisableScope(frame.get()); -#endif + if (!frame->editor().willUnapplyEditing(*this)) + return; - size_t size = m_commands.size(); - for (size_t i = size; i; --i) - m_commands[i - 1]->doUnapply(); - } + size_t size = m_commands.size(); + for (size_t i = size; i; --i) + m_commands[i - 1]->doUnapply(); + + frame->editor().unappliedEditing(*this); - frame->editor().unappliedEditing(this); + if (AXObjectCache::accessibilityEnabled()) + m_replacedText.postTextStateChangeNotificationForUnapply(m_document->existingAXObjectCache()); } void EditCommandComposition::reapply() { ASSERT(m_document); RefPtr<Frame> frame = m_document->frame(); - ASSERT(frame); + if (!frame) + return; + + m_replacedText.captureTextForReapply(); // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>. // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one // if one is necessary (like for the creation of VisiblePositions). m_document->updateLayoutIgnorePendingStylesheets(); - { -#if ENABLE(DELETION_UI) - DeleteButtonControllerDisableScope deleteButtonControllerDisableScope(frame.get()); -#endif - size_t size = m_commands.size(); - for (size_t i = 0; i != size; ++i) - m_commands[i]->doReapply(); - } - - frame->editor().reappliedEditing(this); + if (!frame->editor().willReapplyEditing(*this)) + return; + + for (auto& command : m_commands) + command->doReapply(); + + frame->editor().reappliedEditing(*this); + + if (AXObjectCache::accessibilityEnabled()) + m_replacedText.postTextStateChangeNotificationForReapply(m_document->existingAXObjectCache()); } void EditCommandComposition::append(SimpleEditCommand* command) @@ -160,20 +280,26 @@ void EditCommandComposition::setStartingSelection(const VisibleSelection& select { m_startingSelection = selection; m_startingRootEditableElement = selection.rootEditableElement(); + m_replacedText.configureRangeDeletedByReapplyWithStartingSelection(selection); } void EditCommandComposition::setEndingSelection(const VisibleSelection& selection) { m_endingSelection = selection; m_endingRootEditableElement = selection.rootEditableElement(); + m_replacedText.configureRangeDeletedByReapplyWithEndingSelection(selection); +} + +void EditCommandComposition::setRangeDeletedByUnapply(const VisiblePositionIndexRange& range) +{ + m_replacedText.setRangeDeletedByUnapply(range); } #ifndef NDEBUG void EditCommandComposition::getNodesInCommand(HashSet<Node*>& nodes) { - size_t size = m_commands.size(); - for (size_t i = 0; i < size; ++i) - m_commands[i]->getNodesInCommand(nodes); + for (auto& command : m_commands) + command->getNodesInCommand(nodes); } #endif @@ -182,8 +308,8 @@ void applyCommand(PassRefPtr<CompositeEditCommand> command) command->apply(); } -CompositeEditCommand::CompositeEditCommand(Document& document) - : EditCommand(document) +CompositeEditCommand::CompositeEditCommand(Document& document, EditAction editingAction) + : EditCommand(document, editingAction) { } @@ -192,20 +318,39 @@ CompositeEditCommand::~CompositeEditCommand() ASSERT(isTopLevelCommand() || !m_composition); } +bool CompositeEditCommand::willApplyCommand() +{ + return frame().editor().willApplyEditing(*this, targetRangesForBindings()); +} + void CompositeEditCommand::apply() { if (!endingSelection().isContentRichlyEditable()) { switch (editingAction()) { - case EditActionTyping: + case EditActionTypingDeleteSelection: + case EditActionTypingDeleteBackward: + case EditActionTypingDeleteForward: + case EditActionTypingDeleteWordBackward: + case EditActionTypingDeleteWordForward: + case EditActionTypingDeleteLineBackward: + case EditActionTypingDeleteLineForward: + case EditActionTypingDeletePendingComposition: + case EditActionTypingDeleteFinalComposition: + case EditActionTypingInsertText: + case EditActionTypingInsertLineBreak: + case EditActionTypingInsertParagraph: + case EditActionTypingInsertPendingComposition: + case EditActionTypingInsertFinalComposition: case EditActionPaste: - case EditActionDrag: + case EditActionDeleteByDrag: case EditActionSetWritingDirection: case EditActionCut: case EditActionUnspecified: -#if PLATFORM(IOS) + case EditActionInsert: + case EditActionInsertReplacement: + case EditActionInsertFromDrop: case EditActionDelete: case EditActionDictation: -#endif break; default: ASSERT_NOT_REACHED(); @@ -219,29 +364,66 @@ void CompositeEditCommand::apply() // if one is necessary (like for the creation of VisiblePositions). document().updateLayoutIgnorePendingStylesheets(); + if (!willApplyCommand()) + return; + { - EventQueueScope scope; -#if ENABLE(DELETION_UI) - DeleteButtonControllerDisableScope deleteButtonControllerDisableScope(&frame()); -#endif + EventQueueScope eventQueueScope; doApply(); } - // Only need to call appliedEditing for top-level commands, - // and TypingCommands do it on their own (see TypingCommand::typingAddedToOpenCommand). - if (!isTypingCommand()) - frame().editor().appliedEditing(this); + didApplyCommand(); setShouldRetainAutocorrectionIndicator(false); } -EditCommandComposition* CompositeEditCommand::ensureComposition() +void CompositeEditCommand::didApplyCommand() +{ + frame().editor().appliedEditing(this); +} + +Vector<RefPtr<StaticRange>> CompositeEditCommand::targetRanges() const +{ + ASSERT(!isEditingTextAreaOrTextInput()); + auto firstRange = frame().selection().selection().firstRange(); + if (!firstRange) + return { }; + + RefPtr<StaticRange> range = StaticRange::createFromRange(*firstRange); + return { 1, range }; +} + +Vector<RefPtr<StaticRange>> CompositeEditCommand::targetRangesForBindings() const { - CompositeEditCommand* command = this; - while (command && command->parent()) - command = command->parent(); + if (isEditingTextAreaOrTextInput()) + return { }; + + return targetRanges(); +} + +RefPtr<DataTransfer> CompositeEditCommand::inputEventDataTransfer() const +{ + return nullptr; +} + +EditCommandComposition* CompositeEditCommand::composition() const +{ + for (auto* command = this; command; command = command->parent()) { + if (auto composition = command->m_composition) { + ASSERT(!command->parent()); + return composition.get(); + } + } + return nullptr; +} + +EditCommandComposition& CompositeEditCommand::ensureComposition() +{ + auto* command = this; + while (auto* parent = command->parent()) + command = parent; if (!command->m_composition) command->m_composition = EditCommandComposition::create(document(), startingSelection(), endingSelection(), editingAction()); - return command->m_composition.get(); + return *command->m_composition; } bool CompositeEditCommand::isCreateLinkCommand() const @@ -268,6 +450,11 @@ void CompositeEditCommand::setShouldRetainAutocorrectionIndicator(bool) { } +String CompositeEditCommand::inputEventTypeName() const +{ + return inputTypeNameForEditingAction(editingAction()); +} + // // sugary-sweet convenience functions to help create and apply edit commands in composite commands // @@ -277,10 +464,10 @@ void CompositeEditCommand::applyCommandToComposite(PassRefPtr<EditCommand> prpCo command->setParent(this); command->doApply(); if (command->isSimpleEditCommand()) { - command->setParent(0); - ensureComposition()->append(toSimpleEditCommand(command.get())); + command->setParent(nullptr); + ensureComposition().append(toSimpleEditCommand(command.get())); } - m_commands.append(command.release()); + m_commands.append(WTFMove(command)); } void CompositeEditCommand::applyCommandToComposite(PassRefPtr<CompositeEditCommand> command, const VisibleSelection& selection) @@ -316,7 +503,7 @@ void CompositeEditCommand::removeStyledElement(PassRefPtr<Element> element) void CompositeEditCommand::insertParagraphSeparator(bool useDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea) { - applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), useDefaultParagraphElement, pasteBlockqutoeIntoUnquotedArea)); + applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), useDefaultParagraphElement, pasteBlockqutoeIntoUnquotedArea, editingAction())); } void CompositeEditCommand::insertLineBreak() @@ -326,14 +513,15 @@ void CompositeEditCommand::insertLineBreak() bool CompositeEditCommand::isRemovableBlock(const Node* node) { - if (!node->hasTagName(divTag)) + ASSERT(node); + if (!is<HTMLDivElement>(*node)) return false; Node* parentNode = node->parentNode(); if (parentNode && parentNode->firstChild() != parentNode->lastChild()) return false; - if (!toElement(node)->hasAttributes()) + if (!downcast<HTMLDivElement>(*node).hasAttributes()) return true; return false; @@ -341,17 +529,17 @@ bool CompositeEditCommand::isRemovableBlock(const Node* node) void CompositeEditCommand::insertNodeBefore(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) { - ASSERT(!refChild->hasTagName(bodyTag)); - applyCommandToComposite(InsertNodeBeforeCommand::create(insertChild, refChild, shouldAssumeContentIsAlwaysEditable)); + applyCommandToComposite(InsertNodeBeforeCommand::create(insertChild, refChild, shouldAssumeContentIsAlwaysEditable, editingAction())); } void CompositeEditCommand::insertNodeAfter(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild) { ASSERT(insertChild); ASSERT(refChild); - ASSERT(!refChild->hasTagName(bodyTag)); ContainerNode* parent = refChild->parentNode(); - ASSERT(parent); + if (!parent) + return; + ASSERT(!parent->isShadowRoot()); if (parent->lastChild() == refChild) appendNode(insertChild, parent); @@ -370,21 +558,21 @@ void CompositeEditCommand::insertNodeAt(PassRefPtr<Node> insertChild, const Posi Node* refChild = p.deprecatedNode(); int offset = p.deprecatedEditingOffset(); - if (canHaveChildrenForEditing(refChild)) { + if (canHaveChildrenForEditing(*refChild)) { Node* child = refChild->firstChild(); for (int i = 0; child && i < offset; i++) child = child->nextSibling(); if (child) insertNodeBefore(insertChild, child); else - appendNode(insertChild, toContainerNode(refChild)); - } else if (caretMinOffset(refChild) >= offset) + appendNode(insertChild, downcast<ContainerNode>(refChild)); + } else if (caretMinOffset(*refChild) >= offset) insertNodeBefore(insertChild, refChild); - else if (refChild->isTextNode() && caretMaxOffset(refChild) > offset) { - splitTextNode(toText(refChild), offset); + else if (is<Text>(*refChild) && caretMaxOffset(*refChild) > offset) { + splitTextNode(downcast<Text>(refChild), offset); // Mutation events (bug 22634) from the text node insertion may have removed the refChild - if (!refChild->inDocument()) + if (!refChild->isConnected()) return; insertNodeBefore(insertChild, refChild); } else @@ -393,39 +581,39 @@ void CompositeEditCommand::insertNodeAt(PassRefPtr<Node> insertChild, const Posi void CompositeEditCommand::appendNode(PassRefPtr<Node> node, PassRefPtr<ContainerNode> parent) { - ASSERT(canHaveChildrenForEditing(parent.get())); - applyCommandToComposite(AppendNodeCommand::create(parent, node)); + ASSERT(canHaveChildrenForEditing(*parent)); + ASSERT(node); + applyCommandToComposite(AppendNodeCommand::create(parent, *node, editingAction())); } void CompositeEditCommand::removeChildrenInRange(PassRefPtr<Node> node, unsigned from, unsigned to) { Vector<RefPtr<Node>> children; - Node* child = node->childNode(from); + Node* child = node->traverseToChildAt(from); for (unsigned i = from; child && i < to; i++, child = child->nextSibling()) children.append(child); - size_t size = children.size(); - for (size_t i = 0; i < size; ++i) - removeNode(children[i].release()); + for (auto& child : children) + removeNode(WTFMove(child)); } void CompositeEditCommand::removeNode(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) { if (!node || !node->nonShadowBoundaryParentNode()) return; - applyCommandToComposite(RemoveNodeCommand::create(node, shouldAssumeContentIsAlwaysEditable)); + applyCommandToComposite(RemoveNodeCommand::create(*node, shouldAssumeContentIsAlwaysEditable, editingAction())); } void CompositeEditCommand::removeNodePreservingChildren(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) { - applyCommandToComposite(RemoveNodePreservingChildrenCommand::create(node, shouldAssumeContentIsAlwaysEditable)); + applyCommandToComposite(RemoveNodePreservingChildrenCommand::create(node, shouldAssumeContentIsAlwaysEditable, editingAction())); } void CompositeEditCommand::removeNodeAndPruneAncestors(PassRefPtr<Node> node) { RefPtr<ContainerNode> parent = node->parentNode(); removeNode(node); - prune(parent.release()); + prune(WTFMove(parent)); } void CompositeEditCommand::moveRemainingSiblingsToNewParent(Node* node, Node* pastLastNodeToMove, PassRefPtr<Element> prpNewParent) @@ -436,13 +624,13 @@ void CompositeEditCommand::moveRemainingSiblingsToNewParent(Node* node, Node* pa for (; node && node != pastLastNodeToMove; node = node->nextSibling()) nodesToRemove.append(*node); - for (unsigned i = 0; i < nodesToRemove.size(); i++) { - removeNode(&nodesToRemove[i].get()); - appendNode(&nodesToRemove[i].get(), newParent); + for (auto& nodeToRemove : nodesToRemove) { + removeNode(nodeToRemove.ptr()); + appendNode(nodeToRemove.ptr(), newParent); } } -void CompositeEditCommand::updatePositionForNodeRemovalPreservingChildren(Position& position, Node* node) +void CompositeEditCommand::updatePositionForNodeRemovalPreservingChildren(Position& position, Node& node) { int offset = (position.anchorType() == Position::PositionIsOffsetInAnchor) ? position.offsetInContainerNode() : 0; updatePositionForNodeRemoval(position, node); @@ -460,14 +648,14 @@ HTMLElement* CompositeEditCommand::replaceElementWithSpanPreservingChildrenAndAt // Returning a raw pointer here is OK because the command is retained by // applyCommandToComposite (thus retaining the span), and the span is also // in the DOM tree, and thus alive whie it has a parent. - ASSERT(command->spanElement()->inDocument()); + ASSERT(command->spanElement()->isConnected()); return command->spanElement(); } void CompositeEditCommand::prune(PassRefPtr<Node> node) { if (RefPtr<Node> highestNodeToRemove = highestNodeToRemoveInPruning(node.get())) - removeNode(highestNodeToRemove.release()); + removeNode(WTFMove(highestNodeToRemove)); } void CompositeEditCommand::splitTextNode(PassRefPtr<Text> node, unsigned offset) @@ -492,7 +680,7 @@ void CompositeEditCommand::mergeIdenticalElements(PassRefPtr<Element> prpFirst, applyCommandToComposite(MergeIdenticalElementsCommand::create(first, second)); } -void CompositeEditCommand::wrapContentsInDummySpan(PassRefPtr<Element> element) +void CompositeEditCommand::wrapContentsInDummySpan(Element& element) { applyCommandToComposite(WrapContentsInDummySpanCommand::create(element)); } @@ -502,7 +690,6 @@ void CompositeEditCommand::splitTextNodeContainingElement(PassRefPtr<Text> text, applyCommandToComposite(SplitTextNodeContainingElementCommand::create(text, offset)); } -#if PLATFORM(IOS) void CompositeEditCommand::inputText(const String& text, bool selectInsertedText) { unsigned offset = 0; @@ -543,25 +730,24 @@ void CompositeEditCommand::inputText(const String& text, bool selectInsertedText if (selectInsertedText) setEndingSelection(VisibleSelection(visiblePositionForIndex(startIndex, scope.get()), visiblePositionForIndex(startIndex + length, scope.get()))); } -#endif void CompositeEditCommand::insertTextIntoNode(PassRefPtr<Text> node, unsigned offset, const String& text) { if (!text.isEmpty()) - applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, text)); + applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, text, editingAction())); } void CompositeEditCommand::deleteTextFromNode(PassRefPtr<Text> node, unsigned offset, unsigned count) { - applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count)); + applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count, editingAction())); } void CompositeEditCommand::replaceTextInNode(PassRefPtr<Text> prpNode, unsigned offset, unsigned count, const String& replacementText) { RefPtr<Text> node(prpNode); - applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count)); + applyCommandToComposite(DeleteFromTextNodeCommand::create(WTFMove(node), offset, count)); if (!replacementText.isEmpty()) - applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, replacementText)); + applyCommandToComposite(InsertIntoTextNodeCommand::create(WTFMove(node), offset, replacementText, editingAction())); } Position CompositeEditCommand::replaceSelectedTextInNode(const String& text) @@ -574,60 +760,61 @@ Position CompositeEditCommand::replaceSelectedTextInNode(const String& text) RefPtr<Text> textNode = start.containerText(); replaceTextInNode(textNode, start.offsetInContainerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), text); - return Position(textNode.release(), start.offsetInContainerNode() + text.length()); + return Position(textNode.get(), start.offsetInContainerNode() + text.length()); } -static void copyMarkers(const Vector<DocumentMarker*>& markerPointers, Vector<DocumentMarker>& markers) +static Vector<RenderedDocumentMarker> copyMarkers(const Vector<RenderedDocumentMarker*>& markerPointers) { - size_t arraySize = markerPointers.size(); - markers.reserveCapacity(arraySize); - for (size_t i = 0; i < arraySize; ++i) - markers.append(*markerPointers[i]); + Vector<RenderedDocumentMarker> markers; + markers.reserveInitialCapacity(markerPointers.size()); + for (auto& markerPointer : markerPointers) + markers.uncheckedAppend(*markerPointer); + + return markers; } void CompositeEditCommand::replaceTextInNodePreservingMarkers(PassRefPtr<Text> prpNode, unsigned offset, unsigned count, const String& replacementText) { RefPtr<Text> node(prpNode); DocumentMarkerController& markerController = document().markers(); - Vector<DocumentMarker> markers; - copyMarkers(markerController.markersInRange(Range::create(document(), node, offset, node, offset + count).get(), DocumentMarker::AllMarkers()), markers); + auto markers = copyMarkers(markerController.markersInRange(Range::create(document(), node, offset, node, offset + count).ptr(), DocumentMarker::AllMarkers())); replaceTextInNode(node, offset, count, replacementText); RefPtr<Range> newRange = Range::create(document(), node, offset, node, offset + replacementText.length()); - for (size_t i = 0; i < markers.size(); ++i) + for (const auto& marker : markers) #if PLATFORM(IOS) - markerController.addMarker(newRange.get(), markers[i].type(), markers[i].description(), markers[i].alternatives(), markers[i].metadata()); + markerController.addMarker(newRange.get(), marker.type(), marker.description(), marker.alternatives(), marker.metadata()); #else - markerController.addMarker(newRange.get(), markers[i].type(), markers[i].description()); + markerController.addMarker(newRange.get(), marker.type(), marker.description()); #endif // PLATFORM(IOS) } -Position CompositeEditCommand::positionOutsideTabSpan(const Position& pos) +Position CompositeEditCommand::positionOutsideTabSpan(const Position& position) { - if (!isTabSpanTextNode(pos.anchorNode())) - return pos; + if (!isTabSpanTextNode(position.anchorNode())) + return position; - switch (pos.anchorType()) { + switch (position.anchorType()) { case Position::PositionIsBeforeChildren: case Position::PositionIsAfterChildren: ASSERT_NOT_REACHED(); - return pos; + return position; case Position::PositionIsOffsetInAnchor: break; case Position::PositionIsBeforeAnchor: - return positionInParentBeforeNode(pos.anchorNode()); + return positionInParentBeforeNode(position.anchorNode()); case Position::PositionIsAfterAnchor: - return positionInParentAfterNode(pos.anchorNode()); + return positionInParentAfterNode(position.anchorNode()); } - Node* tabSpan = tabSpanNode(pos.containerNode()); + auto* tabSpan = tabSpanNode(position.containerNode()); - if (pos.offsetInContainerNode() <= caretMinOffset(pos.containerNode())) + if (position.offsetInContainerNode() <= caretMinOffset(*position.containerNode())) return positionInParentBeforeNode(tabSpan); - if (pos.offsetInContainerNode() >= caretMaxOffset(pos.containerNode())) + if (position.offsetInContainerNode() >= caretMaxOffset(*position.containerNode())) return positionInParentAfterNode(tabSpan); - splitTextNodeContainingElement(toText(pos.containerNode()), pos.offsetInContainerNode()); + splitTextNodeContainingElement(&downcast<Text>(*position.containerNode()), position.offsetInContainerNode()); return positionInParentBeforeNode(tabSpan); } @@ -637,10 +824,20 @@ void CompositeEditCommand::insertNodeAtTabSpanPosition(PassRefPtr<Node> node, co insertNodeAt(node, positionOutsideTabSpan(pos)); } +static EditAction deleteSelectionEditingActionForEditingAction(EditAction editingAction) +{ + switch (editingAction) { + case EditActionCut: + return EditActionCut; + default: + return EditActionDelete; + } +} + void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup) { if (endingSelection().isRange()) - applyCommandToComposite(DeleteSelectionCommand::create(document(), smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup)); + applyCommandToComposite(DeleteSelectionCommand::create(document(), smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup, deleteSelectionEditingActionForEditingAction(editingAction()))); } void CompositeEditCommand::deleteSelection(const VisibleSelection &selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup) @@ -656,7 +853,7 @@ void CompositeEditCommand::removeCSSProperty(PassRefPtr<StyledElement> element, void CompositeEditCommand::removeNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute) { - setNodeAttribute(element, attribute, AtomicString()); + setNodeAttribute(element, attribute, nullAtom); } void CompositeEditCommand::setNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute, const AtomicString& value) @@ -664,34 +861,33 @@ void CompositeEditCommand::setNodeAttribute(PassRefPtr<Element> element, const Q applyCommandToComposite(SetNodeAttributeCommand::create(element, attribute, value)); } -static inline bool containsOnlyWhitespace(const String& text) +static inline bool containsOnlyDeprecatedEditingWhitespace(const String& text) { for (unsigned i = 0; i < text.length(); ++i) { - if (!isWhitespace(text.deprecatedCharacters()[i])) + if (!deprecatedIsEditingWhitespace(text[i])) return false; } - return true; } bool CompositeEditCommand::shouldRebalanceLeadingWhitespaceFor(const String& text) const { - return containsOnlyWhitespace(text); + return containsOnlyDeprecatedEditingWhitespace(text); } bool CompositeEditCommand::canRebalance(const Position& position) const { Node* node = position.containerNode(); - if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node || !node->isTextNode()) + if (position.anchorType() != Position::PositionIsOffsetInAnchor || !is<Text>(node)) return false; - Text* textNode = toText(node); - if (textNode->length() == 0) + Text& textNode = downcast<Text>(*node); + if (!textNode.length()) return false; node->document().updateStyleIfNeeded(); - RenderObject* renderer = textNode->renderer(); + RenderObject* renderer = textNode.renderer(); if (renderer && !renderer->style().collapseWhiteSpace()) return false; @@ -707,14 +903,14 @@ void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position) // If the rebalance is for the single offset, and neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing. int offset = position.deprecatedEditingOffset(); - String text = toText(node)->data(); - if (!isWhitespace(text[offset])) { + String text = downcast<Text>(*node).data(); + if (!deprecatedIsEditingWhitespace(text[offset])) { offset--; - if (offset < 0 || !isWhitespace(text[offset])) + if (offset < 0 || !deprecatedIsEditingWhitespace(text[offset])) return; } - rebalanceWhitespaceOnTextSubstring(toText(node), position.offsetInContainerNode(), position.offsetInContainerNode()); + rebalanceWhitespaceOnTextSubstring(downcast<Text>(node), position.offsetInContainerNode(), position.offsetInContainerNode()); } void CompositeEditCommand::rebalanceWhitespaceOnTextSubstring(PassRefPtr<Text> prpTextNode, int startOffset, int endOffset) @@ -726,19 +922,19 @@ void CompositeEditCommand::rebalanceWhitespaceOnTextSubstring(PassRefPtr<Text> p // Set upstream and downstream to define the extent of the whitespace surrounding text[offset]. int upstream = startOffset; - while (upstream > 0 && isWhitespace(text[upstream - 1])) + while (upstream > 0 && deprecatedIsEditingWhitespace(text[upstream - 1])) upstream--; int downstream = endOffset; - while ((unsigned)downstream < text.length() && isWhitespace(text[downstream])) + while ((unsigned)downstream < text.length() && deprecatedIsEditingWhitespace(text[downstream])) downstream++; int length = downstream - upstream; if (!length) return; - VisiblePosition visibleUpstreamPos(Position(textNode, upstream)); - VisiblePosition visibleDownstreamPos(Position(textNode, downstream)); + VisiblePosition visibleUpstreamPos(Position(textNode.get(), upstream)); + VisiblePosition visibleDownstreamPos(Position(textNode.get(), downstream)); String string = text.substring(upstream, length); String rebalancedString = stringWithRebalancedWhitespace(string, @@ -748,19 +944,19 @@ void CompositeEditCommand::rebalanceWhitespaceOnTextSubstring(PassRefPtr<Text> p isEndOfParagraph(visibleDownstreamPos) || (unsigned)downstream == text.length()); if (string != rebalancedString) - replaceTextInNodePreservingMarkers(textNode.release(), upstream, length, rebalancedString); + replaceTextInNodePreservingMarkers(WTFMove(textNode), upstream, length, rebalancedString); } void CompositeEditCommand::prepareWhitespaceAtPositionForSplit(Position& position) { Node* node = position.deprecatedNode(); - if (!node || !node->isTextNode()) + if (!is<Text>(node)) return; - Text* textNode = toText(node); + Text& textNode = downcast<Text>(*node); - if (textNode->length() == 0) + if (!textNode.length()) return; - RenderObject* renderer = textNode->renderer(); + RenderObject* renderer = textNode.renderer(); if (renderer && !renderer->style().collapseWhiteSpace()) return; @@ -773,10 +969,10 @@ void CompositeEditCommand::prepareWhitespaceAtPositionForSplit(Position& positio VisiblePosition previousVisiblePos(visiblePos.previous()); Position previous(previousVisiblePos.deepEquivalent()); - if (isCollapsibleWhitespace(previousVisiblePos.characterAfter()) && previous.deprecatedNode()->isTextNode() && !previous.deprecatedNode()->hasTagName(brTag)) - replaceTextInNodePreservingMarkers(toText(previous.deprecatedNode()), previous.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); - if (isCollapsibleWhitespace(visiblePos.characterAfter()) && position.deprecatedNode()->isTextNode() && !position.deprecatedNode()->hasTagName(brTag)) - replaceTextInNodePreservingMarkers(toText(position.deprecatedNode()), position.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); + if (deprecatedIsCollapsibleWhitespace(previousVisiblePos.characterAfter()) && is<Text>(*previous.deprecatedNode()) && !is<HTMLBRElement>(*previous.deprecatedNode())) + replaceTextInNodePreservingMarkers(downcast<Text>(previous.deprecatedNode()), previous.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); + if (deprecatedIsCollapsibleWhitespace(visiblePos.characterAfter()) && is<Text>(*position.deprecatedNode()) && !is<HTMLBRElement>(*position.deprecatedNode())) + replaceTextInNodePreservingMarkers(downcast<Text>(position.deprecatedNode()), position.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); } void CompositeEditCommand::rebalanceWhitespace() @@ -824,7 +1020,7 @@ void CompositeEditCommand::deleteInsignificantText(PassRefPtr<Text> textNode, un return; unsigned removed = 0; - InlineTextBox* prevBox = 0; + InlineTextBox* prevBox = nullptr; String str; // This loop structure works to process all gaps preceding a box, @@ -853,7 +1049,7 @@ void CompositeEditCommand::deleteInsignificantText(PassRefPtr<Text> textNode, un if (++sortedTextBoxesPosition < sortedTextBoxes.size()) box = sortedTextBoxes[sortedTextBoxesPosition]; else - box = 0; + box = nullptr; } } @@ -880,15 +1076,14 @@ void CompositeEditCommand::deleteInsignificantText(const Position& start, const return; Vector<RefPtr<Text>> nodes; - for (Node* node = start.deprecatedNode(); node; node = NodeTraversal::next(node)) { - if (node->isTextNode()) - nodes.append(toText(node)); + for (Node* node = start.deprecatedNode(); node; node = NodeTraversal::next(*node)) { + if (is<Text>(*node)) + nodes.append(downcast<Text>(node)); if (node == end.deprecatedNode()) break; } - for (size_t i = 0; i < nodes.size(); ++i) { - Text* textNode = nodes[i].get(); + for (auto& textNode : nodes) { int startOffset = textNode == start.deprecatedNode() ? start.deprecatedEditingOffset() : 0; int endOffset = textNode == end.deprecatedNode() ? end.deprecatedEditingOffset() : static_cast<int>(textNode->length()); deleteInsignificantText(textNode, startOffset, endOffset); @@ -901,52 +1096,51 @@ void CompositeEditCommand::deleteInsignificantTextDownstream(const Position& pos deleteInsignificantText(pos, end); } -PassRefPtr<Node> CompositeEditCommand::appendBlockPlaceholder(PassRefPtr<Element> container) +RefPtr<Node> CompositeEditCommand::appendBlockPlaceholder(PassRefPtr<Element> container) { if (!container) - return 0; + return nullptr; document().updateLayoutIgnorePendingStylesheets(); // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964. ASSERT(container->renderer()); - RefPtr<Node> placeholder = createBlockPlaceholderElement(document()); - appendNode(placeholder, container); - return placeholder.release(); + auto placeholder = createBlockPlaceholderElement(document()); + appendNode(placeholder.ptr(), container); + return WTFMove(placeholder); } -PassRefPtr<Node> CompositeEditCommand::insertBlockPlaceholder(const Position& pos) +RefPtr<Node> CompositeEditCommand::insertBlockPlaceholder(const Position& pos) { if (pos.isNull()) - return 0; + return nullptr; // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964. ASSERT(pos.deprecatedNode()->renderer()); - RefPtr<Node> placeholder = createBlockPlaceholderElement(document()); - insertNodeAt(placeholder, pos); - return placeholder.release(); + auto placeholder = createBlockPlaceholderElement(document()); + insertNodeAt(placeholder.ptr(), pos); + return WTFMove(placeholder); } -PassRefPtr<Node> CompositeEditCommand::addBlockPlaceholderIfNeeded(Element* container) +RefPtr<Node> CompositeEditCommand::addBlockPlaceholderIfNeeded(Element* container) { if (!container) - return 0; + return nullptr; document().updateLayoutIgnorePendingStylesheets(); - RenderObject* renderer = container->renderer(); - if (!renderer || !renderer->isRenderBlockFlow()) - return 0; + auto* renderer = container->renderer(); + if (!is<RenderBlockFlow>(renderer)) + return nullptr; - // append the placeholder to make sure it follows - // any unrendered blocks - RenderBlock* block = toRenderBlock(renderer); - if (block->height() == 0 || (block->isListItem() && block->isEmpty())) + // Append the placeholder to make sure it follows any unrendered blocks. + auto& blockFlow = downcast<RenderBlockFlow>(*renderer); + if (!blockFlow.height() || (blockFlow.isListItem() && !blockFlow.firstChild())) return appendBlockPlaceholder(container); - return 0; + return nullptr; } // Assumes that the position is at a placeholder and does the removal without much checking. @@ -955,28 +1149,28 @@ void CompositeEditCommand::removePlaceholderAt(const Position& p) ASSERT(lineBreakExistsAtPosition(p)); // We are certain that the position is at a line break, but it may be a br or a preserved newline. - if (p.anchorNode()->hasTagName(brTag)) { + if (is<HTMLBRElement>(*p.anchorNode())) { removeNode(p.anchorNode()); return; } - deleteTextFromNode(toText(p.anchorNode()), p.offsetInContainerNode(), 1); + deleteTextFromNode(downcast<Text>(p.anchorNode()), p.offsetInContainerNode(), 1); } -PassRefPtr<Node> CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position) +Ref<HTMLElement> CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position) { - RefPtr<Element> paragraphElement = createDefaultParagraphElement(document()); - paragraphElement->appendChild(createBreakElement(document()), IGNORE_EXCEPTION); - insertNodeAt(paragraphElement, position); - return paragraphElement.release(); + auto paragraphElement = createDefaultParagraphElement(document()); + paragraphElement->appendChild(HTMLBRElement::create(document())); + insertNodeAt(paragraphElement.ptr(), position); + return paragraphElement; } // If the paragraph is not entirely within it's own block, create one and move the paragraph into // it, and return that block. Otherwise return 0. -PassRefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position& pos) +RefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position& pos) { if (pos.isNull()) - return 0; + return nullptr; document().updateLayoutIgnorePendingStylesheets(); @@ -994,7 +1188,7 @@ PassRefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessar // If there are no VisiblePositions in the same block as pos then // upstreamStart will be outside the paragraph if (comparePositions(pos, upstreamStart) < 0) - return 0; + return nullptr; // Perform some checks to see if we need to perform work in this function. if (isBlock(upstreamStart.deprecatedNode())) { @@ -1003,35 +1197,38 @@ PassRefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessar if (upstreamStart.deprecatedNode() == editableRootForPosition(upstreamStart)) { // If the block is the root editable element and it contains no visible content, create a new // block but don't try and move content into it, since there's nothing for moveParagraphs to move. - if (!Position::hasRenderedNonAnonymousDescendantsWithHeight(toRenderElement(*upstreamStart.deprecatedNode()->renderer()))) + if (!Position::hasRenderedNonAnonymousDescendantsWithHeight(downcast<RenderElement>(*upstreamStart.deprecatedNode()->renderer()))) return insertNewDefaultParagraphElementAt(upstreamStart); } else if (isBlock(upstreamEnd.deprecatedNode())) { if (!upstreamEnd.deprecatedNode()->isDescendantOf(upstreamStart.deprecatedNode())) { // If the paragraph end is a descendant of paragraph start, then we need to run // the rest of this function. If not, we can bail here. - return 0; + return nullptr; } } else if (enclosingBlock(upstreamEnd.deprecatedNode()) != upstreamStart.deprecatedNode()) { - // The visibleEnd. It must be an ancestor of the paragraph start. - // We can bail as we have a full block to work with. - ASSERT(upstreamStart.deprecatedNode()->isDescendantOf(enclosingBlock(upstreamEnd.deprecatedNode()))); - return 0; + // The visibleEnd. If it is an ancestor of the paragraph start, then + // we can bail as we have a full block to work with. + if (upstreamStart.deprecatedNode()->isDescendantOf(enclosingBlock(upstreamEnd.deprecatedNode()))) + return nullptr; } else if (isEndOfEditableOrNonEditableContent(visibleEnd)) { // At the end of the editable region. We can bail here as well. - return 0; + return nullptr; } } - RefPtr<Node> newBlock = insertNewDefaultParagraphElementAt(upstreamStart); + // If upstreamStart is not editable, then we can bail here. + if (!isEditablePosition(upstreamStart)) + return nullptr; + auto newBlock = insertNewDefaultParagraphElementAt(upstreamStart); bool endWasBr = visibleParagraphEnd.deepEquivalent().deprecatedNode()->hasTagName(brTag); - moveParagraphs(visibleParagraphStart, visibleParagraphEnd, VisiblePosition(firstPositionInNode(newBlock.get()))); + moveParagraphs(visibleParagraphStart, visibleParagraphEnd, VisiblePosition(firstPositionInNode(newBlock.ptr()))); if (newBlock->lastChild() && newBlock->lastChild()->hasTagName(brTag) && !endWasBr) removeNode(newBlock->lastChild()); - return newBlock.release(); + return WTFMove(newBlock); } void CompositeEditCommand::pushAnchorElementDown(Element& anchorElement) @@ -1041,7 +1238,7 @@ void CompositeEditCommand::pushAnchorElementDown(Element& anchorElement) setEndingSelection(VisibleSelection::selectionFromContentsOfNode(&anchorElement)); applyStyledElement(&anchorElement); // Clones of anchorElement have been pushed down, now remove it. - if (anchorElement.inDocument()) + if (anchorElement.isConnected()) removeNodePreservingChildren(&anchorElement); } @@ -1059,7 +1256,7 @@ void CompositeEditCommand::cloneParagraphUnderNewElement(const Position& start, if (outerNode->isRootEditableElement()) { lastNode = blockElement; } else { - lastNode = outerNode->cloneNode(isTableElement(outerNode.get())); + lastNode = outerNode->cloneNode(isRenderedTable(outerNode.get())); appendNode(lastNode, blockElement); } @@ -1074,9 +1271,9 @@ void CompositeEditCommand::cloneParagraphUnderNewElement(const Position& start, for (size_t i = ancestors.size(); i != 0; --i) { Node* item = ancestors[i - 1].get(); - RefPtr<Node> child = item->cloneNode(isTableElement(item)); - appendNode(child, toElement(lastNode.get())); - lastNode = child.release(); + auto child = item->cloneNode(isRenderedTable(item)); + appendNode(child.ptr(), downcast<Element>(lastNode.get())); + lastNode = WTFMove(child); } } @@ -1092,7 +1289,7 @@ void CompositeEditCommand::cloneParagraphUnderNewElement(const Position& start, } RefPtr<Node> startNode = start.deprecatedNode(); - for (RefPtr<Node> node = NodeTraversal::nextSkippingChildren(startNode.get(), outerNode.get()); node; node = NodeTraversal::nextSkippingChildren(node.get(), outerNode.get())) { + for (RefPtr<Node> node = NodeTraversal::nextSkippingChildren(*startNode, outerNode.get()); node; node = NodeTraversal::nextSkippingChildren(*node, outerNode.get())) { // Move lastNode up in the tree as much as node was moved up in the // tree by NodeTraversal::nextSkippingChildren, so that the relative depth between // node and the original start node is maintained in the clone. @@ -1101,10 +1298,10 @@ void CompositeEditCommand::cloneParagraphUnderNewElement(const Position& start, lastNode = lastNode->parentNode(); } - RefPtr<Node> clonedNode = node->cloneNode(true); - insertNodeAfter(clonedNode, lastNode); - lastNode = clonedNode.release(); - if (node == end.deprecatedNode() || end.deprecatedNode()->isDescendantOf(node.get())) + auto clonedNode = node->cloneNode(true); + insertNodeAfter(clonedNode.ptr(), lastNode); + lastNode = WTFMove(clonedNode); + if (node == end.deprecatedNode() || end.deprecatedNode()->isDescendantOf(*node)) break; } } @@ -1120,12 +1317,13 @@ void CompositeEditCommand::cloneParagraphUnderNewElement(const Position& start, void CompositeEditCommand::cleanupAfterDeletion(VisiblePosition destination) { VisiblePosition caretAfterDelete = endingSelection().visibleStart(); - if (caretAfterDelete != destination && isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) { + if (!caretAfterDelete.equals(destination) && isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) { // Note: We want the rightmost candidate. Position position = caretAfterDelete.deepEquivalent().downstream(); Node* node = position.deprecatedNode(); + ASSERT(node); // Normally deletion will leave a br as a placeholder. - if (node->hasTagName(brTag)) + if (is<HTMLBRElement>(*node)) removeNodeAndPruneAncestors(node); // If the selection to move was empty and in an empty block that // doesn't require a placeholder to prop itself open (like a bordered @@ -1143,11 +1341,11 @@ void CompositeEditCommand::cleanupAfterDeletion(VisiblePosition destination) else if (lineBreakExistsAtPosition(position)) { // There is a preserved '\n' at caretAfterDelete. // We can safely assume this is a text node. - Text* textNode = toText(node); - if (textNode->length() == 1) + Text& textNode = downcast<Text>(*node); + if (textNode.length() == 1) removeNodeAndPruneAncestors(node); else - deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1); + deleteTextFromNode(&textNode, position.deprecatedEditingOffset(), 1); } } } @@ -1160,6 +1358,9 @@ void CompositeEditCommand::cleanupAfterDeletion(VisiblePosition destination) void CompositeEditCommand::moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode) { + if (startOfParagraphToMove.isNull() || endOfParagraphToMove.isNull()) + return; + ASSERT(outerNode); ASSERT(blockElement); @@ -1192,10 +1393,11 @@ void CompositeEditCommand::moveParagraphWithClones(const VisiblePosition& startO beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent()); afterParagraph = VisiblePosition(afterParagraph.deepEquivalent()); - if (beforeParagraph.isNotNull() && !isTableElement(beforeParagraph.deepEquivalent().deprecatedNode()) - && ((!isEndOfParagraph(beforeParagraph) && !isStartOfParagraph(beforeParagraph)) || beforeParagraph == afterParagraph)) { + if (beforeParagraph.isNotNull() && !isRenderedTable(beforeParagraph.deepEquivalent().deprecatedNode()) + && ((!isEndOfParagraph(beforeParagraph) && !isStartOfParagraph(beforeParagraph)) || beforeParagraph == afterParagraph) + && isEditablePosition(beforeParagraph.deepEquivalent())) { // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal. - insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent()); + insertNodeAt(HTMLBRElement::create(document()), beforeParagraph.deepEquivalent()); } } @@ -1260,7 +1462,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap RefPtr<DocumentFragment> fragment; // This used to use a ternary for initialization, but that confused some versions of GCC, see bug 37912 if (startOfParagraphToMove != endOfParagraphToMove) - fragment = createFragmentFromMarkup(document(), createMarkup(*range, 0, DoNotAnnotateForInterchange, true), ""); + fragment = createFragmentFromMarkup(document(), createMarkup(*range, 0, DoNotAnnotateForInterchange, true), emptyString()); // A non-empty paragraph's style is moved when we copy and move it. We don't move // anything if we're given an empty paragraph, but an empty paragraph can have style @@ -1283,9 +1485,9 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap frame().editor().clearMisspellingsAndBadGrammar(endingSelection()); deleteSelection(false, false, false, false); - ASSERT(destination.deepEquivalent().anchorNode()->inDocument()); + ASSERT(destination.deepEquivalent().anchorNode()->isConnected()); cleanupAfterDeletion(destination); - ASSERT(destination.deepEquivalent().anchorNode()->inDocument()); + ASSERT(destination.deepEquivalent().anchorNode()->isConnected()); // Add a br if pruning an empty block level element caused a collapse. For example: // foo^ @@ -1298,7 +1500,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap afterParagraph = VisiblePosition(afterParagraph.deepEquivalent()); if (beforeParagraph.isNotNull() && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) { // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal. - insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent()); + insertNodeAt(HTMLBRElement::create(document()), beforeParagraph.deepEquivalent()); // Need an updateLayout here in case inserting the br has split a text node. document().updateLayoutIgnorePendingStylesheets(); } @@ -1311,7 +1513,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::MovingParagraph; if (!preserveStyle) options |= ReplaceSelectionCommand::MatchStyle; - applyCommandToComposite(ReplaceSelectionCommand::create(document(), fragment, options)); + applyCommandToComposite(ReplaceSelectionCommand::create(document(), WTFMove(fragment), options)); frame().editor().markMisspellingsAndBadGrammar(endingSelection()); @@ -1333,49 +1535,59 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap } } -// FIXME: Send an appropriate shouldDeleteRange call. -bool CompositeEditCommand::breakOutOfEmptyListItem() +std::optional<VisibleSelection> CompositeEditCommand::shouldBreakOutOfEmptyListItem() const { - RefPtr<Node> emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart()); + auto emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart()); if (!emptyListItem) - return false; - - RefPtr<EditingStyle> style = EditingStyle::create(endingSelection().start()); - style->mergeTypingStyle(document()); + return std::nullopt; - RefPtr<ContainerNode> listNode = emptyListItem->parentNode(); + auto listNode = emptyListItem->parentNode(); // FIXME: Can't we do something better when the immediate parent wasn't a list node? if (!listNode || (!listNode->hasTagName(ulTag) && !listNode->hasTagName(olTag)) || !listNode->hasEditableStyle() || listNode == emptyListItem->rootEditableElement()) + return std::nullopt; + + return VisibleSelection(endingSelection().start().previous(BackwardDeletion), endingSelection().end()); +} + +// FIXME: Send an appropriate shouldDeleteRange call. +bool CompositeEditCommand::breakOutOfEmptyListItem() +{ + if (!shouldBreakOutOfEmptyListItem()) return false; - RefPtr<Element> newBlock = 0; + auto emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart()); + auto listNode = emptyListItem->parentNode(); + auto style = EditingStyle::create(endingSelection().start()); + style->mergeTypingStyle(document()); + + RefPtr<Element> newBlock; if (ContainerNode* blockEnclosingList = listNode->parentNode()) { - if (blockEnclosingList->hasTagName(liTag)) { // listNode is inside another list item - if (visiblePositionAfterNode(blockEnclosingList) == visiblePositionAfterNode(listNode.get())) { + if (is<HTMLLIElement>(*blockEnclosingList)) { // listNode is inside another list item + if (visiblePositionAfterNode(*blockEnclosingList) == visiblePositionAfterNode(*listNode)) { // If listNode appears at the end of the outer list item, then move listNode outside of this list item // e.g. <ul><li>hello <ul><li><br></li></ul> </li></ul> should become <ul><li>hello</li> <ul><li><br></li></ul> </ul> after this section // If listNode does NOT appear at the end, then we should consider it as a regular paragraph. // e.g. <ul><li> <ul><li><br></li></ul> hello</li></ul> should become <ul><li> <div><br></div> hello</li></ul> at the end - splitElement(toElement(blockEnclosingList), listNode); + splitElement(downcast<HTMLLIElement>(blockEnclosingList), listNode); removeNodePreservingChildren(listNode->parentNode()); - newBlock = createListItemElement(document()); + newBlock = HTMLLIElement::create(document()); } // If listNode does NOT appear at the end of the outer list item, then behave as if in a regular paragraph. } else if (blockEnclosingList->hasTagName(olTag) || blockEnclosingList->hasTagName(ulTag)) - newBlock = createListItemElement(document()); + newBlock = HTMLLIElement::create(document()); } if (!newBlock) newBlock = createDefaultParagraphElement(document()); - RefPtr<Node> previousListNode = emptyListItem->isElementNode() ? ElementTraversal::previousSibling(emptyListItem.get()): emptyListItem->previousSibling(); - RefPtr<Node> nextListNode = emptyListItem->isElementNode() ? ElementTraversal::nextSibling(emptyListItem.get()): emptyListItem->nextSibling(); - if (isListItem(nextListNode.get()) || isListElement(nextListNode.get())) { + RefPtr<Node> previousListNode = emptyListItem->isElementNode() ? ElementTraversal::previousSibling(*emptyListItem): emptyListItem->previousSibling(); + RefPtr<Node> nextListNode = emptyListItem->isElementNode() ? ElementTraversal::nextSibling(*emptyListItem): emptyListItem->nextSibling(); + if (isListItem(nextListNode.get()) || isListHTMLElement(nextListNode.get())) { // If emptyListItem follows another list item or nested list, split the list node. - if (isListItem(previousListNode.get()) || isListElement(previousListNode.get())) - splitElement(toElement(listNode.get()), emptyListItem); + if (isListItem(previousListNode.get()) || isListHTMLElement(previousListNode.get())) + splitElement(downcast<Element>(listNode), emptyListItem); // 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. @@ -1386,7 +1598,7 @@ bool CompositeEditCommand::breakOutOfEmptyListItem() // 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(isListItem(previousListNode.get()) || isListElement(previousListNode.get()) ? emptyListItem.get() : listNode.get()); + removeNode(isListItem(previousListNode.get()) || isListHTMLElement(previousListNode.get()) ? emptyListItem : listNode); } appendBlockPlaceholder(newBlock); @@ -1394,7 +1606,7 @@ bool CompositeEditCommand::breakOutOfEmptyListItem() style->prepareToApplyAt(endingSelection().start()); if (!style->isEmpty()) - applyStyle(style.get()); + applyStyle(style.ptr()); return true; } @@ -1410,16 +1622,16 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() Node* highestBlockquote = highestEnclosingNodeOfType(caret.deepEquivalent(), &isMailBlockquote); if (!highestBlockquote) return false; - + if (!isStartOfParagraph(caret) || !isEndOfParagraph(caret)) return false; - + VisiblePosition previous(caret.previous(CannotCrossEditingBoundary)); // Only move forward if there's nothing before the caret, or if there's unquoted content before it. if (enclosingNodeOfType(previous.deepEquivalent(), &isMailBlockquote)) return false; - RefPtr<Node> br = createBreakElement(document()); + RefPtr<Node> br = HTMLBRElement::create(document()); // We want to replace this quoted paragraph with an unquoted one, so insert a br // to hold the caret before the highest blockquote. insertNodeBefore(br, highestBlockquote); @@ -1427,7 +1639,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert // a second one. if (!isStartOfParagraph(atBR)) - insertNodeBefore(createBreakElement(document()), br); + insertNodeBefore(HTMLBRElement::create(document()), br); setEndingSelection(VisibleSelection(atBR, endingSelection().isDirectional())); // If this is an empty paragraph there must be a line break here. @@ -1440,13 +1652,13 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() if (caretPos.deprecatedNode()->hasTagName(brTag)) removeNodeAndPruneAncestors(caretPos.deprecatedNode()); - else if (caretPos.deprecatedNode()->isTextNode()) { + else if (is<Text>(*caretPos.deprecatedNode())) { ASSERT(caretPos.deprecatedEditingOffset() == 0); - Text* textNode = toText(caretPos.deprecatedNode()); - ContainerNode* parentNode = textNode->parentNode(); + Text& textNode = downcast<Text>(*caretPos.deprecatedNode()); + ContainerNode* parentNode = textNode.parentNode(); // The preserved newline must be the first thing in the node, since otherwise the previous // paragraph would be quoted, and we verified that it wasn't above. - deleteTextFromNode(textNode, 0, 1); + deleteTextFromNode(&textNode, 0, 1); prune(parentNode); } @@ -1516,7 +1728,7 @@ Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Posi // Splits the tree parent by parent until we reach the specified ancestor. We use VisiblePositions // to determine if the split is necessary. Returns the last split node. -PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool shouldSplitAncestor) +RefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool shouldSplitAncestor) { ASSERT(start); ASSERT(end); @@ -1528,21 +1740,21 @@ PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, b RefPtr<Node> endNode = end; for (node = start; node && node->parentNode() != endNode; node = node->parentNode()) { - if (!node->parentNode()->isElementNode()) + if (!is<Element>(*node->parentNode())) break; // Do not split a node when doing so introduces an empty node. VisiblePosition positionInParent = firstPositionInNode(node->parentNode()); VisiblePosition positionInNode = firstPositionInOrBeforeNode(node.get()); if (positionInParent != positionInNode) - splitElement(toElement(node->parentNode()), node); + splitElement(downcast<Element>(node->parentNode()), node); } - return node.release(); + return node; } -PassRefPtr<Element> createBlockPlaceholderElement(Document& document) +Ref<Element> createBlockPlaceholderElement(Document& document) { - return document.createElement(brTag, false); + return HTMLBRElement::create(document); } } // namespace WebCore diff --git a/Source/WebCore/editing/CompositeEditCommand.h b/Source/WebCore/editing/CompositeEditCommand.h index 1cd46e2ee..0221dc999 100644 --- a/Source/WebCore/editing/CompositeEditCommand.h +++ b/Source/WebCore/editing/CompositeEditCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,9 +23,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CompositeEditCommand_h -#define CompositeEditCommand_h +#pragma once +#include "AXObjectCache.h" #include "EditCommand.h" #include "CSSPropertyNames.h" #include "UndoStep.h" @@ -34,17 +34,42 @@ namespace WebCore { class EditingStyle; +class DataTransfer; class HTMLElement; +class StaticRange; class StyledElement; class Text; +class AccessibilityUndoReplacedText { +public: + AccessibilityUndoReplacedText() { } + void configureRangeDeletedByReapplyWithStartingSelection(const VisibleSelection&); + void configureRangeDeletedByReapplyWithEndingSelection(const VisibleSelection&); + void setRangeDeletedByUnapply(const VisiblePositionIndexRange&); + + void captureTextForUnapply(); + void captureTextForReapply(); + + void postTextStateChangeNotificationForUnapply(AXObjectCache*); + void postTextStateChangeNotificationForReapply(AXObjectCache*); + +private: + int indexForVisiblePosition(const VisiblePosition&, RefPtr<ContainerNode>&) const; + String textDeletedByUnapply(); + String textDeletedByReapply(); + + String m_replacedText; + VisiblePositionIndexRange m_rangeDeletedByUnapply; + VisiblePositionIndexRange m_rangeDeletedByReapply; +}; + class EditCommandComposition : public UndoStep { public: - static PassRefPtr<EditCommandComposition> create(Document&, const VisibleSelection&, const VisibleSelection&, EditAction); + static Ref<EditCommandComposition> create(Document&, const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction); - virtual void unapply() override; - virtual void reapply() override; - virtual EditAction editingAction() const override { return m_editAction; } + void unapply() override; + void reapply() override; + EditAction editingAction() const override { return m_editAction; } void append(SimpleEditCommand*); bool wasCreateLinkCommand() const { return m_editAction == EditActionCreateLink; } @@ -54,6 +79,7 @@ public: void setEndingSelection(const VisibleSelection&); Element* startingRootEditableElement() const { return m_startingRootEditableElement.get(); } Element* endingRootEditableElement() const { return m_endingRootEditableElement.get(); } + void setRangeDeletedByUnapply(const VisiblePositionIndexRange&); #ifndef NDEBUG virtual void getNodesInCommand(HashSet<Node*>&); @@ -68,6 +94,7 @@ private: Vector<RefPtr<SimpleEditCommand>> m_commands; RefPtr<Element> m_startingRootEditableElement; RefPtr<Element> m_endingRootEditableElement; + AccessibilityUndoReplacedText m_replacedText; EditAction m_editAction; }; @@ -77,8 +104,8 @@ public: void apply(); bool isFirstCommand(EditCommand* command) { return !m_commands.isEmpty() && m_commands.first() == command; } - EditCommandComposition* composition() { return m_composition.get(); } - EditCommandComposition* ensureComposition(); + EditCommandComposition* composition() const; + EditCommandComposition& ensureComposition(); virtual bool isCreateLinkCommand() const; virtual bool isTypingCommand() const; @@ -87,9 +114,21 @@ public: virtual bool shouldRetainAutocorrectionIndicator() const; virtual void setShouldRetainAutocorrectionIndicator(bool); virtual bool shouldStopCaretBlinking() const { return false; } + virtual String inputEventTypeName() const; + virtual String inputEventData() const { return { }; } + virtual bool isBeforeInputEventCancelable() const { return true; } + virtual bool shouldDispatchInputEvents() const { return true; } + Vector<RefPtr<StaticRange>> targetRangesForBindings() const; + virtual RefPtr<DataTransfer> inputEventDataTransfer() const; protected: - explicit CompositeEditCommand(Document&); + explicit CompositeEditCommand(Document&, EditAction = EditActionUnspecified); + + // If willApplyCommand returns false, we won't proceed with applying the command. + virtual bool willApplyCommand(); + virtual void didApplyCommand(); + + virtual Vector<RefPtr<StaticRange>> targetRanges() const; // // sugary-sweet convenience functions to help create and apply edit commands in composite commands @@ -104,9 +143,7 @@ protected: void deleteSelection(bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = true, bool sanitizeMarkup = true); void deleteSelection(const VisibleSelection&, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = true, bool sanitizeMarkup = true); virtual void deleteTextFromNode(PassRefPtr<Text>, unsigned offset, unsigned count); -#if PLATFORM(IOS) void inputText(const String&, bool selectInsertedText = false); -#endif bool isRemovableBlock(const Node*); void insertNodeAfter(PassRefPtr<Node>, PassRefPtr<Node> refChild); void insertNodeAt(PassRefPtr<Node>, const Position&); @@ -130,7 +167,7 @@ protected: void removeNodePreservingChildren(PassRefPtr<Node>, ShouldAssumeContentIsAlwaysEditable = DoNotAssumeContentIsAlwaysEditable); void removeNodeAndPruneAncestors(PassRefPtr<Node>); void moveRemainingSiblingsToNewParent(Node*, Node* pastLastNodeToMove, PassRefPtr<Element> prpNewParent); - void updatePositionForNodeRemovalPreservingChildren(Position&, Node*); + void updatePositionForNodeRemovalPreservingChildren(Position&, Node&); void prune(PassRefPtr<Node>); void replaceTextInNode(PassRefPtr<Text>, unsigned offset, unsigned count, const String& replacementText); Position replaceSelectedTextInNode(const String&); @@ -140,20 +177,20 @@ protected: void splitElement(PassRefPtr<Element>, PassRefPtr<Node> atChild); void splitTextNode(PassRefPtr<Text>, unsigned offset); void splitTextNodeContainingElement(PassRefPtr<Text>, unsigned offset); - void wrapContentsInDummySpan(PassRefPtr<Element>); + void wrapContentsInDummySpan(Element&); void deleteInsignificantText(PassRefPtr<Text>, unsigned start, unsigned end); void deleteInsignificantText(const Position& start, const Position& end); void deleteInsignificantTextDownstream(const Position&); - PassRefPtr<Node> appendBlockPlaceholder(PassRefPtr<Element>); - PassRefPtr<Node> insertBlockPlaceholder(const Position&); - PassRefPtr<Node> addBlockPlaceholderIfNeeded(Element*); + RefPtr<Node> appendBlockPlaceholder(PassRefPtr<Element>); + RefPtr<Node> insertBlockPlaceholder(const Position&); + RefPtr<Node> addBlockPlaceholderIfNeeded(Element*); void removePlaceholderAt(const Position&); - PassRefPtr<Node> insertNewDefaultParagraphElementAt(const Position&); + Ref<HTMLElement> insertNewDefaultParagraphElementAt(const Position&); - PassRefPtr<Node> moveParagraphContentsToNewBlockIfNecessary(const Position&); + RefPtr<Node> moveParagraphContentsToNewBlockIfNecessary(const Position&); void pushAnchorElementDown(Element&); @@ -163,17 +200,18 @@ protected: void cloneParagraphUnderNewElement(const Position& start, const Position& end, Node* outerNode, Element* blockElement); void cleanupAfterDeletion(VisiblePosition destination = VisiblePosition()); + std::optional<VisibleSelection> shouldBreakOutOfEmptyListItem() const; bool breakOutOfEmptyListItem(); bool breakOutOfEmptyMailBlockquotedParagraph(); Position positionAvoidingSpecialElementBoundary(const Position&); - PassRefPtr<Node> splitTreeToNode(Node*, Node*, bool splitAncestor = false); + RefPtr<Node> splitTreeToNode(Node*, Node*, bool splitAncestor = false); Vector<RefPtr<EditCommand>> m_commands; private: - virtual bool isCompositeEditCommand() const override { return true; } + bool isCompositeEditCommand() const override { return true; } RefPtr<EditCommandComposition> m_composition; }; @@ -188,5 +226,3 @@ inline CompositeEditCommand* toCompositeEditCommand(EditCommand* command) } } // namespace WebCore - -#endif // CompositeEditCommand_h diff --git a/Source/WebCore/editing/CreateLinkCommand.cpp b/Source/WebCore/editing/CreateLinkCommand.cpp index e8d02ca9b..3a0520873 100644 --- a/Source/WebCore/editing/CreateLinkCommand.cpp +++ b/Source/WebCore/editing/CreateLinkCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2006 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -43,16 +43,16 @@ void CreateLinkCommand::doApply() if (endingSelection().isNone()) return; - RefPtr<HTMLAnchorElement> anchorElement = HTMLAnchorElement::create(document()); + auto anchorElement = HTMLAnchorElement::create(document()); anchorElement->setHref(m_url); if (endingSelection().isRange()) - applyStyledElement(anchorElement.release()); + applyStyledElement(WTFMove(anchorElement)); else { - insertNodeAt(anchorElement.get(), endingSelection().start()); - RefPtr<Text> textNode = Text::create(document(), m_url); - appendNode(textNode.release(), anchorElement.get()); - setEndingSelection(VisibleSelection(positionInParentBeforeNode(anchorElement.get()), positionInParentAfterNode(anchorElement.get()), DOWNSTREAM, endingSelection().isDirectional())); + insertNodeAt(anchorElement.ptr(), endingSelection().start()); + auto textNode = Text::create(document(), m_url); + appendNode(WTFMove(textNode), anchorElement.ptr()); + setEndingSelection(VisibleSelection(positionInParentBeforeNode(anchorElement.ptr()), positionInParentAfterNode(anchorElement.ptr()), DOWNSTREAM, endingSelection().isDirectional())); } } diff --git a/Source/WebCore/editing/CreateLinkCommand.h b/Source/WebCore/editing/CreateLinkCommand.h index ba79c0966..39b98d0dc 100644 --- a/Source/WebCore/editing/CreateLinkCommand.h +++ b/Source/WebCore/editing/CreateLinkCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CreateLinkCommand_h -#define CreateLinkCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -32,22 +31,20 @@ namespace WebCore { class CreateLinkCommand : public CompositeEditCommand { public: - static PassRefPtr<CreateLinkCommand> create(Document& document, const String& linkURL) + static Ref<CreateLinkCommand> create(Document& document, const String& linkURL) { - return adoptRef(new CreateLinkCommand(document, linkURL)); + return adoptRef(*new CreateLinkCommand(document, linkURL)); } - bool isCreateLinkCommand() const { return true; } + bool isCreateLinkCommand() const override { return true; } private: CreateLinkCommand(Document&, const String& linkURL); - virtual void doApply(); - virtual EditAction editingAction() const { return EditActionCreateLink; } + void doApply() override; + EditAction editingAction() const override { return EditActionCreateLink; } String m_url; }; } // namespace WebCore - -#endif // CreateLinkCommand_h diff --git a/Source/WebCore/editing/DeleteButton.cpp b/Source/WebCore/editing/DeleteButton.cpp deleted file mode 100644 index f6b367141..000000000 --- a/Source/WebCore/editing/DeleteButton.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2006, 2010 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. - */ - -#include "config.h" -#include "DeleteButton.h" - -#include "DeleteButtonController.h" -#include "Document.h" -#include "Event.h" -#include "EventNames.h" -#include "HTMLNames.h" - -namespace WebCore { - -using namespace HTMLNames; - -#if ENABLE(DELETION_UI) - -inline DeleteButton::DeleteButton(Document& document) - : HTMLImageElement(imgTag, document) -{ -} - -PassRefPtr<DeleteButton> DeleteButton::create(Document& document) -{ - return adoptRef(new DeleteButton(document)); -} - -void DeleteButton::defaultEventHandler(Event* event) -{ - if (event->type() == eventNames().clickEvent) { - document().frame()->editor().deleteButtonController().deleteTarget(); - event->setDefaultHandled(); - return; - } - - HTMLImageElement::defaultEventHandler(event); -} -#endif - -} // namespace diff --git a/Source/WebCore/editing/DeleteButtonController.cpp b/Source/WebCore/editing/DeleteButtonController.cpp deleted file mode 100644 index a78769c3f..000000000 --- a/Source/WebCore/editing/DeleteButtonController.cpp +++ /dev/null @@ -1,396 +0,0 @@ -/* - * Copyright (C) 2006, 2008, 2009 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. - */ - -#include "config.h" -#include "DeleteButtonController.h" - -#include "CachedImage.h" -#include "CSSPrimitiveValue.h" -#include "CompositeEditCommand.h" -#include "Document.h" -#include "EditorClient.h" -#include "htmlediting.h" -#include "HTMLDivElement.h" -#include "HTMLNames.h" -#include "Image.h" -#include "Node.h" -#include "Page.h" -#include "RemoveNodeCommand.h" -#include "RenderBox.h" -#include "StyleProperties.h" - -namespace WebCore { - -using namespace HTMLNames; - -#if ENABLE(DELETION_UI) - -const char* const DeleteButtonController::containerElementIdentifier = "WebKit-Editing-Delete-Container"; -const char* const DeleteButtonController::buttonElementIdentifier = "WebKit-Editing-Delete-Button"; -const char* const DeleteButtonController::outlineElementIdentifier = "WebKit-Editing-Delete-Outline"; - -DeleteButtonController::DeleteButtonController(Frame& frame) - : m_frame(frame) - , m_wasStaticPositioned(false) - , m_wasAutoZIndex(false) - , m_disableStack(0) -{ -} - -static bool isDeletableElement(const Node* node) -{ - if (!node || !node->isHTMLElement() || !node->inDocument() || !node->hasEditableStyle()) - return false; - - // In general we want to only draw the UI around object of a certain area, but we still keep the min width/height to - // make sure we don't end up with very thin or very short elements getting the UI. - const int minimumArea = 2500; - const int minimumWidth = 48; - const int minimumHeight = 16; - const unsigned minimumVisibleBorders = 1; - - RenderObject* renderer = node->renderer(); - if (!renderer || !renderer->isBox()) - return false; - - // Disallow the body element since it isn't practical to delete, and the deletion UI would be clipped. - if (node->hasTagName(bodyTag)) - return false; - - // Disallow elements with any overflow clip, since the deletion UI would be clipped as well. <rdar://problem/6840161> - if (renderer->hasOverflowClip()) - return false; - - // Disallow Mail blockquotes since the deletion UI would get in the way of editing for these. - if (isMailBlockquote(node)) - return false; - - RenderBox* box = toRenderBox(renderer); - IntRect borderBoundingBox = box->borderBoundingBox(); - if (borderBoundingBox.width() < minimumWidth || borderBoundingBox.height() < minimumHeight) - return false; - - if ((borderBoundingBox.width() * borderBoundingBox.height()) < minimumArea) - return false; - - if (box->isTable()) - return true; - - if (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(iframeTag)) - return true; - - if (box->isOutOfFlowPositioned()) - return true; - - if (box->isRenderBlock() && !box->isTableCell()) { - const RenderStyle& style = box->style(); - - // Allow blocks that have background images - if (style.hasBackgroundImage()) { - for (const FillLayer* background = style.backgroundLayers(); background; background = background->next()) { - if (background->image() && background->image()->canRender(box, 1)) - return true; - } - } - - // Allow blocks with a minimum number of non-transparent borders - unsigned visibleBorders = style.borderTop().isVisible() + style.borderBottom().isVisible() + style.borderLeft().isVisible() + style.borderRight().isVisible(); - if (visibleBorders >= minimumVisibleBorders) - return true; - - // Allow blocks that have a different background from it's parent - ContainerNode* parentNode = node->parentNode(); - if (!parentNode) - return false; - - auto parentRenderer = parentNode->renderer(); - if (!parentRenderer) - return false; - - const RenderStyle& parentStyle = parentRenderer->style(); - - if (box->hasBackground() && (!parentRenderer->hasBackground() || style.visitedDependentColor(CSSPropertyBackgroundColor) != parentStyle.visitedDependentColor(CSSPropertyBackgroundColor))) - return true; - } - - return false; -} - -static HTMLElement* enclosingDeletableElement(const VisibleSelection& selection) -{ - if (!selection.isContentEditable()) - return 0; - - RefPtr<Range> range = selection.toNormalizedRange(); - if (!range) - return 0; - - Node* container = range->commonAncestorContainer(ASSERT_NO_EXCEPTION); - ASSERT(container); - - // The enclosingNodeOfType function only works on nodes that are editable - // (which is strange, given its name). - if (!container->hasEditableStyle()) - return 0; - - Node* element = enclosingNodeOfType(firstPositionInNode(container), &isDeletableElement); - return element && element->isHTMLElement() ? toHTMLElement(element) : 0; -} - -void DeleteButtonController::respondToChangedSelection(const VisibleSelection& oldSelection) -{ - if (!enabled()) - return; - - HTMLElement* oldElement = enclosingDeletableElement(oldSelection); - HTMLElement* newElement = enclosingDeletableElement(m_frame.selection().selection()); - if (oldElement == newElement) - return; - - // If the base is inside a deletable element, give the element a delete widget. - if (newElement) - show(newElement); - else - hide(); -} - -void DeleteButtonController::deviceScaleFactorChanged() -{ - if (!enabled()) - return; - - HTMLElement* currentTarget = m_target.get(); - hide(); - - // Setting m_containerElement to 0 will force the deletionUI to be re-created with - // artwork of the appropriate resolution in show(). - m_containerElement = 0; - show(currentTarget); -} - -void DeleteButtonController::createDeletionUI() -{ - RefPtr<HTMLDivElement> container = HTMLDivElement::create(m_target->document()); - container->setIdAttribute(containerElementIdentifier); - - container->setInlineStyleProperty(CSSPropertyWebkitUserDrag, CSSValueNone); - container->setInlineStyleProperty(CSSPropertyWebkitUserSelect, CSSValueNone); - container->setInlineStyleProperty(CSSPropertyWebkitUserModify, CSSValueReadOnly); - container->setInlineStyleProperty(CSSPropertyVisibility, CSSValueHidden); - container->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute); - container->setInlineStyleProperty(CSSPropertyCursor, CSSValueDefault); - container->setInlineStyleProperty(CSSPropertyTop, 0, CSSPrimitiveValue::CSS_PX); - container->setInlineStyleProperty(CSSPropertyRight, 0, CSSPrimitiveValue::CSS_PX); - container->setInlineStyleProperty(CSSPropertyBottom, 0, CSSPrimitiveValue::CSS_PX); - container->setInlineStyleProperty(CSSPropertyLeft, 0, CSSPrimitiveValue::CSS_PX); - - RefPtr<HTMLDivElement> outline = HTMLDivElement::create(m_target->document()); - outline->setIdAttribute(outlineElementIdentifier); - - const int borderWidth = 4; - const int borderRadius = 6; - - outline->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute); - outline->setInlineStyleProperty(CSSPropertyZIndex, ASCIILiteral("-1000000")); - outline->setInlineStyleProperty(CSSPropertyTop, -borderWidth - m_target->renderBox()->borderTop(), CSSPrimitiveValue::CSS_PX); - outline->setInlineStyleProperty(CSSPropertyRight, -borderWidth - m_target->renderBox()->borderRight(), CSSPrimitiveValue::CSS_PX); - outline->setInlineStyleProperty(CSSPropertyBottom, -borderWidth - m_target->renderBox()->borderBottom(), CSSPrimitiveValue::CSS_PX); - outline->setInlineStyleProperty(CSSPropertyLeft, -borderWidth - m_target->renderBox()->borderLeft(), CSSPrimitiveValue::CSS_PX); - outline->setInlineStyleProperty(CSSPropertyBorderWidth, borderWidth, CSSPrimitiveValue::CSS_PX); - outline->setInlineStyleProperty(CSSPropertyBorderStyle, CSSValueSolid); - outline->setInlineStyleProperty(CSSPropertyBorderColor, ASCIILiteral("rgba(0, 0, 0, 0.6)")); - outline->setInlineStyleProperty(CSSPropertyBorderRadius, borderRadius, CSSPrimitiveValue::CSS_PX); - outline->setInlineStyleProperty(CSSPropertyVisibility, CSSValueVisible); - - ExceptionCode ec = 0; - container->appendChild(outline.get(), ec); - ASSERT(!ec); - if (ec) - return; - - RefPtr<DeleteButton> button = DeleteButton::create(m_target->document()); - button->setIdAttribute(buttonElementIdentifier); - - const int buttonWidth = 30; - const int buttonHeight = 30; - const int buttonBottomShadowOffset = 2; - - button->setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute); - button->setInlineStyleProperty(CSSPropertyZIndex, ASCIILiteral("1000000")); - button->setInlineStyleProperty(CSSPropertyTop, (-buttonHeight / 2) - m_target->renderBox()->borderTop() - (borderWidth / 2) + buttonBottomShadowOffset, CSSPrimitiveValue::CSS_PX); - button->setInlineStyleProperty(CSSPropertyLeft, (-buttonWidth / 2) - m_target->renderBox()->borderLeft() - (borderWidth / 2), CSSPrimitiveValue::CSS_PX); - button->setInlineStyleProperty(CSSPropertyWidth, buttonWidth, CSSPrimitiveValue::CSS_PX); - button->setInlineStyleProperty(CSSPropertyHeight, buttonHeight, CSSPrimitiveValue::CSS_PX); - button->setInlineStyleProperty(CSSPropertyVisibility, CSSValueVisible); - - float deviceScaleFactor = WebCore::deviceScaleFactor(&m_frame); - RefPtr<Image> buttonImage; - if (deviceScaleFactor >= 2) - buttonImage = Image::loadPlatformResource("deleteButton@2x"); - else - buttonImage = Image::loadPlatformResource("deleteButton"); - - if (buttonImage->isNull()) - return; - - button->setCachedImage(new CachedImage(buttonImage.get())); - - container->appendChild(button.get(), ec); - ASSERT(!ec); - if (ec) - return; - - m_containerElement = container.release(); - m_outlineElement = outline.release(); - m_buttonElement = button.release(); -} - -void DeleteButtonController::show(HTMLElement* element) -{ - hide(); - - if (!enabled() || !element || !element->inDocument() || !isDeletableElement(element)) - return; - - EditorClient* client = m_frame.editor().client(); - if (!client || !client->shouldShowDeleteInterface(element)) - return; - - // we rely on the renderer having current information, so we should update the layout if needed - m_frame.document()->updateLayoutIgnorePendingStylesheets(); - - m_target = element; - - if (!m_containerElement) { - createDeletionUI(); - if (!m_containerElement) { - hide(); - return; - } - } - - ExceptionCode ec = 0; - m_target->appendChild(m_containerElement.get(), ec); - ASSERT(!ec); - if (ec) { - hide(); - return; - } - - if (m_target->renderer()->style().position() == StaticPosition) { - m_target->setInlineStyleProperty(CSSPropertyPosition, CSSValueRelative); - m_wasStaticPositioned = true; - } - - if (m_target->renderer()->style().hasAutoZIndex()) { - m_target->setInlineStyleProperty(CSSPropertyZIndex, ASCIILiteral("0")); - m_wasAutoZIndex = true; - } -} - -void DeleteButtonController::hide() -{ - m_outlineElement = 0; - m_buttonElement = 0; - - if (m_containerElement && m_containerElement->parentNode()) - m_containerElement->parentNode()->removeChild(m_containerElement.get(), IGNORE_EXCEPTION); - - if (m_target) { - if (m_wasStaticPositioned) - m_target->setInlineStyleProperty(CSSPropertyPosition, CSSValueStatic); - if (m_wasAutoZIndex) - m_target->setInlineStyleProperty(CSSPropertyZIndex, CSSValueAuto); - } - - m_wasStaticPositioned = false; - m_wasAutoZIndex = false; -} - -void DeleteButtonController::enable() -{ -#if !PLATFORM(IOS) - ASSERT(m_disableStack > 0); - if (m_disableStack > 0) - m_disableStack--; - if (enabled()) { - // Determining if the element is deletable currently depends on style - // because whether something is editable depends on style, so we need - // to recalculate style before calling enclosingDeletableElement. - m_frame.document()->updateStyleIfNeeded(); - show(enclosingDeletableElement(m_frame.selection().selection())); - } -#endif -} - -void DeleteButtonController::disable() -{ -#if !PLATFORM(IOS) - if (enabled()) - hide(); - m_disableStack++; -#endif -} - -class RemoveTargetCommand : public CompositeEditCommand { -public: - static PassRefPtr<RemoveTargetCommand> create(Document& document, PassRefPtr<Node> target) - { - return adoptRef(new RemoveTargetCommand(document, target)); - } - -private: - RemoveTargetCommand(Document& document, PassRefPtr<Node> target) - : CompositeEditCommand(document) - , m_target(target) - { } - - void doApply() - { - removeNode(m_target); - } - -private: - RefPtr<Node> m_target; -}; - -void DeleteButtonController::deleteTarget() -{ - if (!enabled() || !m_target) - return; - - hide(); - - // Because the deletion UI only appears when the selection is entirely - // within the target, we unconditionally update the selection to be - // a caret where the target had been. - Position pos = positionInParentBeforeNode(m_target.get()); - ASSERT(m_frame.document()); - applyCommand(RemoveTargetCommand::create(*m_frame.document(), m_target)); - m_frame.selection().setSelection(VisiblePosition(pos)); -} -#endif - -} // namespace WebCore diff --git a/Source/WebCore/editing/DeleteButtonController.h b/Source/WebCore/editing/DeleteButtonController.h deleted file mode 100644 index 447dd95ef..000000000 --- a/Source/WebCore/editing/DeleteButtonController.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2006, 2007 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 DeleteButtonController_h -#define DeleteButtonController_h - -#if ENABLE(DELETION_UI) - -#include "DeleteButton.h" -#include "Editor.h" -#include "Frame.h" - -namespace WebCore { - -class DeleteButton; -class HTMLElement; -class RenderObject; -class VisibleSelection; - -class DeleteButtonController { - WTF_MAKE_NONCOPYABLE(DeleteButtonController); WTF_MAKE_FAST_ALLOCATED; -public: - explicit DeleteButtonController(Frame&); - - HTMLElement* containerElement() const { return m_containerElement.get(); } - - void respondToChangedSelection(const VisibleSelection& oldSelection); - - void deviceScaleFactorChanged(); - - void show(HTMLElement*); - void hide(); - - void deleteTarget(); - -private: - static const char* const buttonElementIdentifier; - static const char* const outlineElementIdentifier; - static const char* const containerElementIdentifier; - - void enable(); - void disable(); - friend class DeleteButtonControllerDisableScope; - - void createDeletionUI(); - bool enabled() const { return (!m_disableStack); } - - Frame& m_frame; - RefPtr<HTMLElement> m_target; - RefPtr<HTMLElement> m_containerElement; - RefPtr<HTMLElement> m_outlineElement; - RefPtr<DeleteButton> m_buttonElement; - bool m_wasStaticPositioned; - bool m_wasAutoZIndex; - unsigned m_disableStack; -}; - -class DeleteButtonControllerDisableScope { -public: - DeleteButtonControllerDisableScope(Frame* frame) - : m_frame(frame) - { - if (frame) - frame->editor().deleteButtonController().disable(); - } - - ~DeleteButtonControllerDisableScope() - { - if (m_frame) - m_frame->editor().deleteButtonController().enable(); - } - -private: - RefPtr<Frame> m_frame; -}; - -} // namespace WebCore - -#endif // ENABLE(DELETION_UI) - -#endif // DeleteButtonController_h diff --git a/Source/WebCore/editing/DeleteFromTextNodeCommand.cpp b/Source/WebCore/editing/DeleteFromTextNodeCommand.cpp index a9bcfbcf9..7aaac39ca 100644 --- a/Source/WebCore/editing/DeleteFromTextNodeCommand.cpp +++ b/Source/WebCore/editing/DeleteFromTextNodeCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2005, 2008, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -25,16 +25,15 @@ #include "config.h" #include "DeleteFromTextNodeCommand.h" -#include "Document.h" -#include "ExceptionCodePlaceholder.h" -#include "AXObjectCache.h" +#include "Document.h" #include "Text.h" +#include "htmlediting.h" namespace WebCore { -DeleteFromTextNodeCommand::DeleteFromTextNodeCommand(PassRefPtr<Text> node, unsigned offset, unsigned count) - : SimpleEditCommand(node->document()) +DeleteFromTextNodeCommand::DeleteFromTextNodeCommand(RefPtr<Text>&& node, unsigned offset, unsigned count, EditAction editingAction) + : SimpleEditCommand(node->document(), editingAction) , m_node(node) , m_offset(offset) , m_count(count) @@ -48,19 +47,14 @@ void DeleteFromTextNodeCommand::doApply() { ASSERT(m_node); - if (!m_node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) + if (!isEditableNode(*m_node)) return; - ExceptionCode ec = 0; - m_text = m_node->substringData(m_offset, m_count, ec); - if (ec) + auto result = m_node->substringData(m_offset, m_count); + if (result.hasException()) return; - - // Need to notify this before actually deleting the text - if (AXObjectCache* cache = document().existingAXObjectCache()) - cache->nodeTextChangeNotification(m_node.get(), AXObjectCache::AXTextDeleted, m_offset, m_text); - - m_node->deleteData(m_offset, m_count, ec); + m_text = result.releaseReturnValue(); + m_node->deleteData(m_offset, m_count); } void DeleteFromTextNodeCommand::doUnapply() @@ -70,10 +64,7 @@ void DeleteFromTextNodeCommand::doUnapply() if (!m_node->hasEditableStyle()) return; - m_node->insertData(m_offset, m_text, IGNORE_EXCEPTION); - - if (AXObjectCache* cache = document().existingAXObjectCache()) - cache->nodeTextChangeNotification(m_node.get(), AXObjectCache::AXTextInserted, m_offset, m_text); + m_node->insertData(m_offset, m_text); } #ifndef NDEBUG diff --git a/Source/WebCore/editing/DeleteFromTextNodeCommand.h b/Source/WebCore/editing/DeleteFromTextNodeCommand.h index 05b23cc76..90c5145d3 100644 --- a/Source/WebCore/editing/DeleteFromTextNodeCommand.h +++ b/Source/WebCore/editing/DeleteFromTextNodeCommand.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005, 2006, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2005, 2006, 2008, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef DeleteFromTextNodeCommand_h -#define DeleteFromTextNodeCommand_h +#pragma once #include "EditCommand.h" @@ -34,19 +33,20 @@ class Text; class DeleteFromTextNodeCommand : public SimpleEditCommand { public: - static PassRefPtr<DeleteFromTextNodeCommand> create(PassRefPtr<Text> node, unsigned offset, unsigned count) + static Ref<DeleteFromTextNodeCommand> create(RefPtr<Text>&& node, unsigned offset, unsigned count, EditAction editingAction = EditActionDelete) { - return adoptRef(new DeleteFromTextNodeCommand(node, offset, count)); + return adoptRef(*new DeleteFromTextNodeCommand(WTFMove(node), offset, count, editingAction)); } -private: - DeleteFromTextNodeCommand(PassRefPtr<Text>, unsigned offset, unsigned count); +protected: + DeleteFromTextNodeCommand(RefPtr<Text>&&, unsigned offset, unsigned count, EditAction); - virtual void doApply() override; - virtual void doUnapply() override; +private: + void doApply() override; + void doUnapply() override; #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override; + void getNodesInCommand(HashSet<Node*>&) override; #endif RefPtr<Text> m_node; @@ -56,5 +56,3 @@ private: }; } // namespace WebCore - -#endif // DeleteFromTextNodeCommand_h diff --git a/Source/WebCore/editing/DeleteSelectionCommand.cpp b/Source/WebCore/editing/DeleteSelectionCommand.cpp index 2b5457fc3..0917b8bd1 100644 --- a/Source/WebCore/editing/DeleteSelectionCommand.cpp +++ b/Source/WebCore/editing/DeleteSelectionCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2005 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -30,17 +30,20 @@ #include "DocumentMarkerController.h" #include "Editor.h" #include "EditorClient.h" -#include "Element.h" +#include "ElementIterator.h" #include "Frame.h" -#include "htmlediting.h" -#include "HTMLInputElement.h" +#include "HTMLBRElement.h" +#include "HTMLLinkElement.h" #include "HTMLNames.h" +#include "HTMLStyleElement.h" #include "HTMLTableElement.h" #include "NodeTraversal.h" #include "RenderTableCell.h" #include "RenderText.h" +#include "RenderedDocumentMarker.h" #include "Text.h" #include "VisibleUnits.h" +#include "htmlediting.h" namespace WebCore { @@ -69,8 +72,8 @@ static bool isTableRowEmpty(Node* row) return true; } -DeleteSelectionCommand::DeleteSelectionCommand(Document& document, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup) - : CompositeEditCommand(document) +DeleteSelectionCommand::DeleteSelectionCommand(Document& document, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup, EditAction editingAction) + : CompositeEditCommand(document, editingAction) , m_hasSelectionToDelete(false) , m_smartDelete(smartDelete) , m_mergeBlocksAfterDelete(mergeBlocksAfterDelete) @@ -80,15 +83,11 @@ DeleteSelectionCommand::DeleteSelectionCommand(Document& document, bool smartDel , m_pruneStartBlockIfNecessary(false) , m_startsAtEmptyLine(false) , m_sanitizeMarkup(sanitizeMarkup) - , m_startBlock(0) - , m_endBlock(0) - , m_typingStyle(0) - , m_deleteIntoBlockquoteStyle(0) { } -DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup) - : CompositeEditCommand(selection.start().anchorNode()->document()) +DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup, EditAction editingAction) + : CompositeEditCommand(selection.start().anchorNode()->document(), editingAction) , m_hasSelectionToDelete(true) , m_smartDelete(smartDelete) , m_mergeBlocksAfterDelete(mergeBlocksAfterDelete) @@ -99,17 +98,13 @@ DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection , m_startsAtEmptyLine(false) , m_sanitizeMarkup(sanitizeMarkup) , m_selectionToDelete(selection) - , m_startBlock(0) - , m_endBlock(0) - , m_typingStyle(0) - , m_deleteIntoBlockquoteStyle(0) { } void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end) { - Node* startSpecialContainer = 0; - Node* endSpecialContainer = 0; + HTMLElement* startSpecialContainer = nullptr; + HTMLElement* endSpecialContainer = nullptr; start = m_selectionToDelete.start(); end = m_selectionToDelete.end(); @@ -126,8 +121,8 @@ void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end) return; while (1) { - startSpecialContainer = 0; - endSpecialContainer = 0; + startSpecialContainer = nullptr; + endSpecialContainer = nullptr; Position s = positionBeforeContainingSpecialElement(start, &startSpecialContainer); Position e = positionAfterContainingSpecialElement(end, &endSpecialContainer); @@ -182,6 +177,11 @@ void DeleteSelectionCommand::initializePositionData() Position start, end; initializeStartEnd(start, end); + if (!isEditablePosition(start, ContentIsEditable)) + start = firstEditablePositionAfterPositionInRoot(start, highestEditableRoot(start)); + if (!isEditablePosition(end, ContentIsEditable)) + end = lastEditablePositionBeforePositionInRoot(end, highestEditableRoot(start)); + m_upstreamStart = start.upstream(); m_downstreamStart = start.downstream(); m_upstreamEnd = end.upstream(); @@ -299,7 +299,7 @@ void DeleteSelectionCommand::saveTypingStyleState() if (enclosingNodeOfType(m_selectionToDelete.start(), isMailBlockquote)) m_deleteIntoBlockquoteStyle = EditingStyle::create(m_selectionToDelete.end()); else - m_deleteIntoBlockquoteStyle = 0; + m_deleteIntoBlockquoteStyle = nullptr; } bool DeleteSelectionCommand::handleSpecialCaseBRDelete() @@ -341,10 +341,41 @@ static Position firstEditablePositionInNode(Node* node) ASSERT(node); Node* next = node; while (next && !next->hasEditableStyle()) - next = NodeTraversal::next(next, node); + next = NodeTraversal::next(*next, node); return next ? firstPositionInOrBeforeNode(next) : Position(); } +void DeleteSelectionCommand::insertBlockPlaceholderForTableCellIfNeeded(Element& element) +{ + // Make sure empty cell has some height. + auto* renderer = element.renderer(); + if (!is<RenderTableCell>(renderer)) + return; + if (downcast<RenderTableCell>(*renderer).contentHeight() > 0) + return; + insertBlockPlaceholder(firstEditablePositionInNode(&element)); +} + +void DeleteSelectionCommand::removeNodeUpdatingStates(Node& node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) +{ + if (&node == m_startBlock && !isEndOfBlock(VisiblePosition(firstPositionInNode(m_startBlock.get())).previous())) + m_needPlaceholder = true; + else if (&node == m_endBlock && !isStartOfBlock(VisiblePosition(lastPositionInNode(m_startBlock.get())).next())) + m_needPlaceholder = true; + + // FIXME: Update the endpoints of the range being deleted. + updatePositionForNodeRemoval(m_endingPosition, node); + updatePositionForNodeRemoval(m_leadingWhitespace, node); + updatePositionForNodeRemoval(m_trailingWhitespace, node); + + CompositeEditCommand::removeNode(&node, shouldAssumeContentIsAlwaysEditable); +} + +static inline bool shouldRemoveContentOnly(const Node& node) +{ + return isTableStructureNode(&node) || node.isRootEditableElement(); +} + void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) { if (!node) @@ -372,38 +403,34 @@ void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node, ShouldAssumeConte } } - if (isTableStructureNode(node.get()) || node->isRootEditableElement()) { + if (shouldRemoveContentOnly(*node)) { // Do not remove an element of table structure; remove its contents. // Likewise for the root editable element. - Node* child = node->firstChild(); + auto* child = NodeTraversal::next(*node, node.get()); while (child) { - Node* remove = child; - child = child->nextSibling(); - removeNode(remove, shouldAssumeContentIsAlwaysEditable); + if (shouldRemoveContentOnly(*child)) { + child = NodeTraversal::next(*child, node.get()); + continue; + } + auto* remove = child; + child = NodeTraversal::nextSkippingChildren(*child, node.get()); + removeNodeUpdatingStates(*remove, shouldAssumeContentIsAlwaysEditable); } - // Make sure empty cell has some height, if a placeholder can be inserted. + ASSERT(is<Element>(*node)); + auto& element = downcast<Element>(*node); document().updateLayoutIgnorePendingStylesheets(); - RenderObject *r = node->renderer(); - if (r && r->isTableCell() && toRenderTableCell(r)->contentHeight() <= 0) { - Position firstEditablePosition = firstEditablePositionInNode(node.get()); - if (firstEditablePosition.isNotNull()) - insertBlockPlaceholder(firstEditablePosition); + // Check if we need to insert a placeholder for descendant table cells. + auto* descendant = ElementTraversal::next(element, &element); + while (descendant) { + auto* placeholderCandidate = descendant; + descendant = ElementTraversal::next(*descendant, &element); + insertBlockPlaceholderForTableCellIfNeeded(*placeholderCandidate); } + insertBlockPlaceholderForTableCellIfNeeded(element); return; } - - if (node == m_startBlock && !isEndOfBlock(VisiblePosition(firstPositionInNode(m_startBlock.get())).previous())) - m_needPlaceholder = true; - else if (node == m_endBlock && !isStartOfBlock(VisiblePosition(lastPositionInNode(m_startBlock.get())).next())) - m_needPlaceholder = true; - - // FIXME: Update the endpoints of the range being deleted. - updatePositionForNodeRemoval(m_endingPosition, node.get()); - updatePositionForNodeRemoval(m_leadingWhitespace, node.get()); - updatePositionForNodeRemoval(m_trailingWhitespace, node.get()); - - CompositeEditCommand::removeNode(node, shouldAssumeContentIsAlwaysEditable); + removeNodeUpdatingStates(*node, shouldAssumeContentIsAlwaysEditable); } static void updatePositionForTextRemoval(Node* node, int offset, int count, Position& position) @@ -433,9 +460,9 @@ void DeleteSelectionCommand::makeStylingElementsDirectChildrenOfEditableRootToPr RefPtr<Range> range = m_selectionToDelete.toNormalizedRange(); RefPtr<Node> node = range->firstNode(); while (node && node != range->pastLastNode()) { - RefPtr<Node> nextNode = NodeTraversal::next(node.get()); - if ((node->hasTagName(styleTag) && !(toElement(node.get())->hasAttribute(scopedAttr))) || node->hasTagName(linkTag)) { - nextNode = NodeTraversal::nextSkippingChildren(node.get()); + RefPtr<Node> nextNode = NodeTraversal::next(*node); + if ((is<HTMLStyleElement>(*node) && !downcast<HTMLStyleElement>(*node).hasAttributeWithoutSynchronization(scopedAttr)) || is<HTMLLinkElement>(*node)) { + nextNode = NodeTraversal::nextSkippingChildren(*node); RefPtr<ContainerNode> rootEditableElement = node->rootEditableElement(); if (rootEditableElement) { removeNode(node); @@ -457,21 +484,22 @@ void DeleteSelectionCommand::handleGeneralDelete() makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss(); // Never remove the start block unless it's a table, in which case we won't merge content in. - if (startNode == m_startBlock && startOffset == 0 && canHaveChildrenForEditing(startNode) && !isHTMLTableElement(startNode)) { + if (startNode == m_startBlock && !startOffset && canHaveChildrenForEditing(*startNode) && !is<HTMLTableElement>(*startNode)) { startOffset = 0; - startNode = NodeTraversal::next(startNode); + startNode = NodeTraversal::next(*startNode); if (!startNode) return; } - if (startOffset >= caretMaxOffset(startNode) && startNode->isTextNode()) { - Text* text = toText(startNode); - if (text->length() > (unsigned)caretMaxOffset(startNode)) - deleteTextFromNode(text, caretMaxOffset(startNode), text->length() - caretMaxOffset(startNode)); + int startNodeCaretMaxOffset = caretMaxOffset(*startNode); + if (startOffset >= startNodeCaretMaxOffset && is<Text>(*startNode)) { + Text& text = downcast<Text>(*startNode); + if (text.length() > static_cast<unsigned>(startNodeCaretMaxOffset)) + deleteTextFromNode(&text, startNodeCaretMaxOffset, text.length() - startNodeCaretMaxOffset); } - if (startOffset >= lastOffsetForEditing(startNode)) { - startNode = NodeTraversal::nextSkippingChildren(startNode); + if (startOffset >= lastOffsetForEditing(*startNode)) { + startNode = NodeTraversal::nextSkippingChildren(*startNode); startOffset = 0; } @@ -481,10 +509,10 @@ void DeleteSelectionCommand::handleGeneralDelete() if (startNode == m_downstreamEnd.deprecatedNode()) { if (m_downstreamEnd.deprecatedEditingOffset() - startOffset > 0) { - if (startNode->isTextNode()) { + if (is<Text>(*startNode)) { // in a text node that needs to be trimmed - Text* text = toText(startNode); - deleteTextFromNode(text, startOffset, m_downstreamEnd.deprecatedEditingOffset() - startOffset); + Text& text = downcast<Text>(*startNode); + deleteTextFromNode(&text, startOffset, m_downstreamEnd.deprecatedEditingOffset() - startOffset); } else { removeChildrenInRange(startNode, startOffset, m_downstreamEnd.deprecatedEditingOffset()); m_endingPosition = m_upstreamStart; @@ -501,51 +529,51 @@ void DeleteSelectionCommand::handleGeneralDelete() RefPtr<Node> node(startNode); if (startOffset > 0) { - if (startNode->isTextNode()) { + if (is<Text>(*startNode)) { // in a text node that needs to be trimmed - Text* text = toText(node.get()); - deleteTextFromNode(text, startOffset, text->length() - startOffset); - node = NodeTraversal::next(node.get()); + Text& text = downcast<Text>(*node); + deleteTextFromNode(&text, startOffset, text.length() - startOffset); + node = NodeTraversal::next(*node); } else { - node = startNode->childNode(startOffset); + node = startNode->traverseToChildAt(startOffset); } - } else if (startNode == m_upstreamEnd.deprecatedNode() && startNode->isTextNode()) { - Text* text = toText(m_upstreamEnd.deprecatedNode()); - deleteTextFromNode(text, 0, m_upstreamEnd.deprecatedEditingOffset()); + } else if (startNode == m_upstreamEnd.deprecatedNode() && is<Text>(*startNode)) { + Text& text = downcast<Text>(*m_upstreamEnd.deprecatedNode()); + deleteTextFromNode(&text, 0, m_upstreamEnd.deprecatedEditingOffset()); } // handle deleting all nodes that are completely selected while (node && node != m_downstreamEnd.deprecatedNode()) { if (comparePositions(firstPositionInOrBeforeNode(node.get()), m_downstreamEnd) >= 0) { // NodeTraversal::nextSkippingChildren just blew past the end position, so stop deleting - node = 0; - } else if (!m_downstreamEnd.deprecatedNode()->isDescendantOf(node.get())) { - RefPtr<Node> nextNode = NodeTraversal::nextSkippingChildren(node.get()); + node = nullptr; + } else if (!m_downstreamEnd.deprecatedNode()->isDescendantOf(*node)) { + RefPtr<Node> nextNode = NodeTraversal::nextSkippingChildren(*node); // if we just removed a node from the end container, update end position so the // check above will work - updatePositionForNodeRemoval(m_downstreamEnd, node.get()); + updatePositionForNodeRemoval(m_downstreamEnd, *node); removeNode(node.get()); node = nextNode.get(); } else { Node* n = node->lastDescendant(); - if (m_downstreamEnd.deprecatedNode() == n && m_downstreamEnd.deprecatedEditingOffset() >= caretMaxOffset(n)) { + if (m_downstreamEnd.deprecatedNode() == n && m_downstreamEnd.deprecatedEditingOffset() >= caretMaxOffset(*n)) { removeNode(node.get()); - node = 0; + node = nullptr; } else - node = NodeTraversal::next(node.get()); + node = NodeTraversal::next(*node); } } - if (m_downstreamEnd.deprecatedNode() != startNode && !m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode()) && m_downstreamEnd.anchorNode()->inDocument() && m_downstreamEnd.deprecatedEditingOffset() >= caretMinOffset(m_downstreamEnd.deprecatedNode())) { - if (m_downstreamEnd.atLastEditingPositionForNode() && !canHaveChildrenForEditing(m_downstreamEnd.deprecatedNode())) { + if (m_downstreamEnd.deprecatedNode() != startNode && !m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode()) && m_downstreamEnd.anchorNode()->isConnected() && m_downstreamEnd.deprecatedEditingOffset() >= caretMinOffset(*m_downstreamEnd.deprecatedNode())) { + if (m_downstreamEnd.atLastEditingPositionForNode() && !canHaveChildrenForEditing(*m_downstreamEnd.deprecatedNode())) { // The node itself is fully selected, not just its contents. Delete it. removeNode(m_downstreamEnd.deprecatedNode()); } else { - if (m_downstreamEnd.deprecatedNode()->isTextNode()) { + if (is<Text>(*m_downstreamEnd.deprecatedNode())) { // in a text node that needs to be trimmed - Text* text = toText(m_downstreamEnd.deprecatedNode()); + Text& text = downcast<Text>(*m_downstreamEnd.deprecatedNode()); if (m_downstreamEnd.deprecatedEditingOffset() > 0) { - deleteTextFromNode(text, 0, m_downstreamEnd.deprecatedEditingOffset()); + deleteTextFromNode(&text, 0, m_downstreamEnd.deprecatedEditingOffset()); } // Remove children of m_downstreamEnd.deprecatedNode() that come after m_upstreamStart. // Don't try to remove children if m_upstreamStart was inside m_downstreamEnd.deprecatedNode() @@ -553,14 +581,14 @@ void DeleteSelectionCommand::handleGeneralDelete() // know how many children to remove. // FIXME: Make m_upstreamStart a position we update as we remove content, then we can // always know which children to remove. - } else if (!(startNodeWasDescendantOfEndNode && !m_upstreamStart.anchorNode()->inDocument())) { - int offset = 0; + } else if (!(startNodeWasDescendantOfEndNode && !m_upstreamStart.anchorNode()->isConnected())) { + unsigned offset = 0; if (m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode())) { Node* n = m_upstreamStart.deprecatedNode(); while (n && n->parentNode() != m_downstreamEnd.deprecatedNode()) n = n->parentNode(); if (n) - offset = n->nodeIndex() + 1; + offset = n->computeNodeIndex() + 1; } removeChildrenInRange(m_downstreamEnd.deprecatedNode(), offset, m_downstreamEnd.deprecatedEditingOffset()); m_downstreamEnd = createLegacyEditingPosition(m_downstreamEnd.deprecatedNode(), offset); @@ -574,15 +602,15 @@ void DeleteSelectionCommand::fixupWhitespace() { document().updateLayoutIgnorePendingStylesheets(); // FIXME: isRenderedCharacter should be removed, and we should use VisiblePosition::characterAfter and VisiblePosition::characterBefore - if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter() && m_leadingWhitespace.deprecatedNode()->isTextNode()) { - Text* textNode = toText(m_leadingWhitespace.deprecatedNode()); - ASSERT(!textNode->renderer() || textNode->renderer()->style().collapseWhiteSpace()); - replaceTextInNodePreservingMarkers(textNode, m_leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); + if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter() && is<Text>(*m_leadingWhitespace.deprecatedNode())) { + Text& textNode = downcast<Text>(*m_leadingWhitespace.deprecatedNode()); + ASSERT(!textNode.renderer() || textNode.renderer()->style().collapseWhiteSpace()); + replaceTextInNodePreservingMarkers(&textNode, m_leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); } - if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter() && m_trailingWhitespace.deprecatedNode()->isTextNode()) { - Text* textNode = toText(m_trailingWhitespace.deprecatedNode()); - ASSERT(!textNode->renderer() || textNode->renderer()->style().collapseWhiteSpace()); - replaceTextInNodePreservingMarkers(textNode, m_trailingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); + if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter() && is<Text>(*m_trailingWhitespace.deprecatedNode())) { + Text& textNode = downcast<Text>(*m_trailingWhitespace.deprecatedNode()); + ASSERT(!textNode.renderer() || textNode.renderer()->style().collapseWhiteSpace()); + replaceTextInNodePreservingMarkers(&textNode, m_trailingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); } } @@ -605,9 +633,9 @@ void DeleteSelectionCommand::mergeParagraphs() ASSERT(!m_pruneStartBlockIfNecessary); // FIXME: Deletion should adjust selection endpoints as it removes nodes so that we never get into this state (4099839). - if (!m_downstreamEnd.anchorNode()->inDocument() || !m_upstreamStart.anchorNode()->inDocument()) + if (!m_downstreamEnd.anchorNode()->isConnected() || !m_upstreamStart.anchorNode()->isConnected()) return; - + // FIXME: The deletion algorithm shouldn't let this happen. if (comparePositions(m_upstreamStart, m_downstreamEnd) > 0) return; @@ -629,7 +657,7 @@ void DeleteSelectionCommand::mergeParagraphs() // We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion. if (!mergeDestination.deepEquivalent().deprecatedNode() || !mergeDestination.deepEquivalent().deprecatedNode()->isDescendantOf(enclosingBlock(m_upstreamStart.containerNode())) || m_startsAtEmptyLine) { - insertNodeAt(createBreakElement(document()).get(), m_upstreamStart); + insertNodeAt(HTMLBRElement::create(document()).ptr(), m_upstreamStart); mergeDestination = VisiblePosition(m_upstreamStart); } @@ -676,7 +704,7 @@ void DeleteSelectionCommand::mergeParagraphs() void DeleteSelectionCommand::removePreviouslySelectedEmptyTableRows() { - if (m_endTableRow && m_endTableRow->inDocument() && m_endTableRow != m_startTableRow) { + if (m_endTableRow && m_endTableRow->isConnected() && m_endTableRow != m_startTableRow) { Node* row = m_endTableRow->previousSibling(); while (row && row != m_startTableRow) { RefPtr<Node> previousRow = row->previousSibling(); @@ -689,7 +717,7 @@ void DeleteSelectionCommand::removePreviouslySelectedEmptyTableRows() } // Remove empty rows after the start row. - if (m_startTableRow && m_startTableRow->inDocument() && m_startTableRow != m_endTableRow) { + if (m_startTableRow && m_startTableRow->isConnected() && m_startTableRow != m_endTableRow) { Node* row = m_startTableRow->nextSibling(); while (row && row != m_endTableRow) { RefPtr<Node> nextRow = row->nextSibling(); @@ -698,17 +726,18 @@ void DeleteSelectionCommand::removePreviouslySelectedEmptyTableRows() row = nextRow.get(); } } - - if (m_endTableRow && m_endTableRow->inDocument() && m_endTableRow != m_startTableRow) + + if (m_endTableRow && m_endTableRow->isConnected() && m_endTableRow != m_startTableRow) { if (isTableRowEmpty(m_endTableRow.get())) { // Don't remove m_endTableRow if it's where we're putting the ending selection. - if (!m_endingPosition.deprecatedNode()->isDescendantOf(m_endTableRow.get())) { + if (!m_endingPosition.deprecatedNode()->isDescendantOf(*m_endTableRow)) { // FIXME: We probably shouldn't remove m_endTableRow unless it's fully selected, even if it is empty. // We'll need to start adjusting the selection endpoints during deletion to know whether or not m_endTableRow // was fully selected here. CompositeEditCommand::removeNode(m_endTableRow.get()); } } + } } void DeleteSelectionCommand::calculateTypingStyleAfterDelete() @@ -725,11 +754,11 @@ void DeleteSelectionCommand::calculateTypingStyleAfterDelete() // If we deleted into a blockquote, but are now no longer in a blockquote, use the alternate typing style if (m_deleteIntoBlockquoteStyle && !enclosingNodeOfType(m_endingPosition, isMailBlockquote, CanCrossEditingBoundary)) m_typingStyle = m_deleteIntoBlockquoteStyle; - m_deleteIntoBlockquoteStyle = 0; + m_deleteIntoBlockquoteStyle = nullptr; m_typingStyle->prepareToApplyAt(m_endingPosition); if (m_typingStyle->isEmpty()) - m_typingStyle = 0; + m_typingStyle = nullptr; // 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 @@ -763,9 +792,8 @@ String DeleteSelectionCommand::originalStringForAutocorrectionAtBeginningOfSelec return String(); RefPtr<Range> rangeOfFirstCharacter = Range::create(document(), startOfSelection.deepEquivalent(), nextPosition.deepEquivalent()); - Vector<DocumentMarker*> markers = document().markers().markersInRange(rangeOfFirstCharacter.get(), DocumentMarker::Autocorrected); - for (size_t i = 0; i < markers.size(); ++i) { - const DocumentMarker* marker = markers[i]; + Vector<RenderedDocumentMarker*> markers = document().markers().markersInRange(rangeOfFirstCharacter.get(), DocumentMarker::Autocorrected); + for (auto* marker : markers) { int startOffset = marker->startOffset(); if (startOffset == startOfSelection.deepEquivalent().offsetInContainerNode()) return marker->description(); @@ -782,7 +810,7 @@ void DeleteSelectionCommand::removeRedundantBlocks() while (node != rootNode) { if (isRemovableBlock(node)) { if (node == m_endingPosition.anchorNode()) - updatePositionForNodeRemovalPreservingChildren(m_endingPosition, node); + updatePositionForNodeRemovalPreservingChildren(m_endingPosition, *node); CompositeEditCommand::removeNodePreservingChildren(node); node = m_endingPosition.anchorNode(); @@ -821,9 +849,10 @@ void DeleteSelectionCommand::doApply() // Don't need a placeholder when deleting a selection that starts just before a table // and ends inside it (we do need placeholders to hold open empty cells, but that's // handled elsewhere). - if (Node* table = isLastPositionBeforeTable(m_selectionToDelete.visibleStart())) - if (m_selectionToDelete.end().deprecatedNode()->isDescendantOf(table)) + if (auto* table = isLastPositionBeforeTable(m_selectionToDelete.visibleStart())) { + if (m_selectionToDelete.end().deprecatedNode()->isDescendantOf(*table)) m_needPlaceholder = false; + } } @@ -853,32 +882,23 @@ void DeleteSelectionCommand::doApply() removePreviouslySelectedEmptyTableRows(); - RefPtr<Node> placeholder = m_needPlaceholder ? createBreakElement(document()).get() : 0; - - if (placeholder) { + if (m_needPlaceholder) { if (m_sanitizeMarkup) removeRedundantBlocks(); - insertNodeAt(placeholder.get(), m_endingPosition); - } - -#if PLATFORM(IOS) - // This checking is due to iphone shows the last entered character momentarily, removing and adding back the - // space when deleting password cause space been showed insecurely. - bool isSecure = NO; - Node* node = m_endingPosition.deprecatedNode(); - if (node && node->isTextNode()) { - Text* textNode = static_cast<Text*>(node); - if (textNode->length() > 0) { - RenderObject* renderer = textNode->renderer(); - isSecure = renderer->style().textSecurity() != TSNONE; - } + insertNodeAt(HTMLBRElement::create(document()), m_endingPosition); } - - if (!isSecure) + + bool shouldRebalaceWhiteSpace = true; + if (!frame().editor().behavior().shouldRebalanceWhiteSpacesInSecureField()) { + Node* node = m_endingPosition.deprecatedNode(); + if (is<Text>(node)) { + Text& textNode = downcast<Text>(*node); + if (textNode.length() && textNode.renderer()) + shouldRebalaceWhiteSpace = textNode.renderer()->style().textSecurity() == TSNONE; + } + } + if (shouldRebalaceWhiteSpace) rebalanceWhitespaceAt(m_endingPosition); -#else - rebalanceWhitespaceAt(m_endingPosition); -#endif calculateTypingStyleAfterDelete(); @@ -889,14 +909,6 @@ void DeleteSelectionCommand::doApply() clearTransientState(); } -EditAction DeleteSelectionCommand::editingAction() const -{ - // Note that DeleteSelectionCommand is also used when the user presses the Delete key, - // but in that case there's a TypingCommand that supplies the editingAction(), so - // the Undo menu correctly shows "Undo Typing" - return EditActionCut; -} - // Normally deletion doesn't preserve the typing style that was present before it. For example, // type a character, Bold, then delete the character and start typing. The Bold typing style shouldn't // stick around. Deletion should preserve a typing style that *it* sets, however. diff --git a/Source/WebCore/editing/DeleteSelectionCommand.h b/Source/WebCore/editing/DeleteSelectionCommand.h index 4c0f723de..c17f6deea 100644 --- a/Source/WebCore/editing/DeleteSelectionCommand.h +++ b/Source/WebCore/editing/DeleteSelectionCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef DeleteSelectionCommand_h -#define DeleteSelectionCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -34,25 +33,24 @@ class EditingStyle; class DeleteSelectionCommand : public CompositeEditCommand { public: - static PassRefPtr<DeleteSelectionCommand> create(Document& document, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = false, bool sanitizeMarkup = true) + static Ref<DeleteSelectionCommand> create(Document& document, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = false, bool sanitizeMarkup = true, EditAction editingAction = EditActionDelete) { - return adoptRef(new DeleteSelectionCommand(document, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup)); + return adoptRef(*new DeleteSelectionCommand(document, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup, editingAction)); } - static PassRefPtr<DeleteSelectionCommand> create(const VisibleSelection& selection, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = false, bool sanitizeMarkup = true) + static Ref<DeleteSelectionCommand> create(const VisibleSelection& selection, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = false, bool sanitizeMarkup = true, EditAction editingAction = EditActionDelete) { - return adoptRef(new DeleteSelectionCommand(selection, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup)); + return adoptRef(*new DeleteSelectionCommand(selection, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup, editingAction)); } protected: - DeleteSelectionCommand(Document&, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool santizeMarkup); + DeleteSelectionCommand(Document&, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool santizeMarkup, EditAction = EditActionDelete); private: - DeleteSelectionCommand(const VisibleSelection&, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup); + DeleteSelectionCommand(const VisibleSelection&, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup, EditAction); - virtual void doApply(); - virtual EditAction editingAction() const; + void doApply() override; - virtual bool preservesTypingStyle() const; + bool preservesTypingStyle() const override; void initializeStartEnd(Position&, Position&); void setStartingSelectionOnSmartDelete(const Position&, const Position&); @@ -68,13 +66,16 @@ private: void calculateTypingStyleAfterDelete(); void clearTransientState(); void makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss(); - virtual void removeNode(PassRefPtr<Node>, ShouldAssumeContentIsAlwaysEditable = DoNotAssumeContentIsAlwaysEditable); - virtual void deleteTextFromNode(PassRefPtr<Text>, unsigned, unsigned); + void removeNode(PassRefPtr<Node>, ShouldAssumeContentIsAlwaysEditable = DoNotAssumeContentIsAlwaysEditable) override; + void deleteTextFromNode(PassRefPtr<Text>, unsigned, unsigned) override; void removeRedundantBlocks(); // This function provides access to original string after the correction has been deleted. String originalStringForAutocorrectionAtBeginningOfSelection(); + void removeNodeUpdatingStates(Node&, ShouldAssumeContentIsAlwaysEditable); + void insertBlockPlaceholderForTableCellIfNeeded(Element&); + bool m_hasSelectionToDelete; bool m_smartDelete; bool m_mergeBlocksAfterDelete; @@ -106,5 +107,3 @@ private: }; } // namespace WebCore - -#endif // DeleteSelectionCommand_h diff --git a/Source/WebCore/editing/DictationAlternative.h b/Source/WebCore/editing/DictationAlternative.h index 08fcea386..fa5cc943e 100644 --- a/Source/WebCore/editing/DictationAlternative.h +++ b/Source/WebCore/editing/DictationAlternative.h @@ -23,15 +23,14 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef DictationAlternative_h -#define DictationAlternative_h +#pragma once #include <stdint.h> namespace WebCore { struct DictationAlternative { - DictationAlternative(unsigned start, unsigned length, uint64_t context); - DictationAlternative(); + WEBCORE_EXPORT DictationAlternative(unsigned start, unsigned length, uint64_t context); + WEBCORE_EXPORT DictationAlternative(); unsigned rangeStart; unsigned rangeLength; @@ -39,6 +38,4 @@ struct DictationAlternative { uint64_t dictationContext; }; -} - -#endif // DictationAlternative_h +} // namespace WebCore diff --git a/Source/WebCore/editing/DictationCommand.cpp b/Source/WebCore/editing/DictationCommand.cpp index 722882591..9b15a8186 100644 --- a/Source/WebCore/editing/DictationCommand.cpp +++ b/Source/WebCore/editing/DictationCommand.cpp @@ -55,17 +55,17 @@ private: class DictationMarkerSupplier : public TextInsertionMarkerSupplier { public: - static PassRefPtr<DictationMarkerSupplier> create(const Vector<DictationAlternative>& alternatives) + static Ref<DictationMarkerSupplier> create(const Vector<DictationAlternative>& alternatives) { - return adoptRef(new DictationMarkerSupplier(alternatives)); + return adoptRef(*new DictationMarkerSupplier(alternatives)); } - virtual void addMarkersToTextNode(Text* textNode, unsigned offsetOfInsertion, const String& textToBeInserted) + void addMarkersToTextNode(Text* textNode, unsigned offsetOfInsertion, const String& textToBeInserted) override { - DocumentMarkerController& markerController = textNode->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)); + auto& markerController = textNode->document().markers(); + for (auto& alternative : m_alternatives) { + DocumentMarker::DictationData data { alternative.dictationContext, textToBeInserted.substring(alternative.rangeStart, alternative.rangeLength) }; + markerController.addMarkerToNode(textNode, alternative.rangeStart + offsetOfInsertion, alternative.rangeLength, DocumentMarker::DictationAlternatives, WTFMove(data)); markerController.addMarkerToNode(textNode, alternative.rangeStart + offsetOfInsertion, alternative.rangeLength, DocumentMarker::SpellCheckingExemption); } } @@ -86,9 +86,9 @@ DictationCommand::DictationCommand(Document& document, const String& text, const { } -void DictationCommand::insertText(Document* document, const String& text, const Vector<DictationAlternative>& alternatives, const VisibleSelection& selectionForInsertion) +void DictationCommand::insertText(Document& document, const String& text, const Vector<DictationAlternative>& alternatives, const VisibleSelection& selectionForInsertion) { - RefPtr<Frame> frame = document->frame(); + RefPtr<Frame> frame = document.frame(); ASSERT(frame); VisibleSelection currentSelection = frame->selection().selection(); @@ -97,25 +97,26 @@ void DictationCommand::insertText(Document* document, const String& text, const RefPtr<DictationCommand> cmd; if (newText == text) - cmd = DictationCommand::create(*document, newText, alternatives); + 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); + cmd = DictationCommand::create(document, newText, Vector<DictationAlternative>()); + applyTextInsertionCommand(frame.get(), *cmd, selectionForInsertion, currentSelection); } void DictationCommand::doApply() { DictationCommandLineOperation operation(this); forEachLineInString(m_textToInsert, operation); + postTextStateChangeNotification(AXTextEditTypeDictation, m_textToInsert); } 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)); + RefPtr<InsertTextCommand> command = InsertTextCommand::createWithMarkerSupplier(document(), m_textToInsert.substring(lineStart, lineLength), DictationMarkerSupplier::create(alternativesInLine), EditActionDictation); applyCommandToComposite(command, endingSelection()); } @@ -124,13 +125,12 @@ void DictationCommand::insertParagraphSeparator() if (!canAppendNewLineFeedToSelection(endingSelection())) return; - applyCommandToComposite(InsertParagraphSeparatorCommand::create(document())); + applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), false, false, EditActionDictation)); } 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]; + for (auto& alternative : m_alternatives) { 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 index 1dee19bf2..13c188772 100644 --- a/Source/WebCore/editing/DictationCommand.h +++ b/Source/WebCore/editing/DictationCommand.h @@ -23,30 +23,27 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef DictationCommand_h -#define DictationCommand_h +#pragma once #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; } + static void insertText(Document&, const String&, const Vector<DictationAlternative>& alternatives, const VisibleSelection&); + bool isDictationCommand() const override { return true; } private: - static PassRefPtr<DictationCommand> create(Document& document, const String& text, const Vector<DictationAlternative>& alternatives) + static Ref<DictationCommand> create(Document& document, const String& text, const Vector<DictationAlternative>& alternatives) { - return adoptRef(new DictationCommand(document, text, alternatives)); + return adoptRef(*new DictationCommand(document, text, alternatives)); } DictationCommand(Document&, const String& text, const Vector<DictationAlternative>& alternatives); - virtual void doApply(); + void doApply() override; void insertTextRunWithoutNewlines(size_t lineStart, size_t lineLength); void insertParagraphSeparator(); @@ -55,6 +52,5 @@ private: String m_textToInsert; Vector<DictationAlternative> m_alternatives; }; -} -#endif // DictationCommand_h +} // namespace WebCore diff --git a/Source/WebCore/editing/DictionaryPopupInfo.h b/Source/WebCore/editing/DictionaryPopupInfo.h new file mode 100644 index 000000000..f1c65a48d --- /dev/null +++ b/Source/WebCore/editing/DictionaryPopupInfo.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014-2015 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. AND ITS 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 APPLE INC. OR ITS 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. + */ + +#pragma once + +#include "FloatPoint.h" +#include "TextIndicator.h" +#include <wtf/RetainPtr.h> + +OBJC_CLASS NSAttributedString; +OBJC_CLASS NSDictionary; + +namespace WebCore { + +struct DictionaryPopupInfo { + FloatPoint origin; + TextIndicatorData textIndicator; +#if PLATFORM(COCOA) + RetainPtr<NSDictionary> options; + RetainPtr<NSAttributedString> attributedString; +#endif +}; + +} // namespace WebCore diff --git a/Source/WebCore/editing/EditAction.h b/Source/WebCore/editing/EditAction.h index 3d397599b..0aba4a5aa 100644 --- a/Source/WebCore/editing/EditAction.h +++ b/Source/WebCore/editing/EditAction.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2004 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,12 +23,14 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef EditAction_h -#define EditAction_h +#pragma once namespace WebCore { typedef enum { EditActionUnspecified, + EditActionInsert, + EditActionInsertReplacement, + EditActionInsertFromDrop, EditActionSetColor, EditActionSetBackgroundColor, EditActionTurnOffKerning, @@ -53,25 +55,35 @@ namespace WebCore { EditActionUnderline, EditActionOutline, EditActionUnscript, - EditActionDrag, + EditActionDeleteByDrag, EditActionCut, EditActionBold, EditActionItalics, -#if PLATFORM(IOS) EditActionDelete, EditActionDictation, -#endif EditActionPaste, EditActionPasteFont, EditActionPasteRuler, - EditActionTyping, + EditActionTypingDeleteSelection, + EditActionTypingDeleteBackward, + EditActionTypingDeleteForward, + EditActionTypingDeleteWordBackward, + EditActionTypingDeleteWordForward, + EditActionTypingDeleteLineBackward, + EditActionTypingDeleteLineForward, + EditActionTypingDeletePendingComposition, + EditActionTypingDeleteFinalComposition, + EditActionTypingInsertText, + EditActionTypingInsertLineBreak, + EditActionTypingInsertParagraph, + EditActionTypingInsertPendingComposition, + EditActionTypingInsertFinalComposition, EditActionCreateLink, EditActionUnlink, EditActionFormatBlock, - EditActionInsertList, + EditActionInsertOrderedList, + EditActionInsertUnorderedList, EditActionIndent, EditActionOutdent } EditAction; -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/editing/EditCommand.cpp b/Source/WebCore/editing/EditCommand.cpp index 104195d03..5ded31647 100644 --- a/Source/WebCore/editing/EditCommand.cpp +++ b/Source/WebCore/editing/EditCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -26,29 +26,107 @@ #include "config.h" #include "EditCommand.h" +#include "AXObjectCache.h" #include "CompositeEditCommand.h" #include "Document.h" #include "Editor.h" #include "Element.h" -#include "EventNames.h" #include "Frame.h" +#include "HTMLInputElement.h" +#include "HTMLTextAreaElement.h" #include "NodeTraversal.h" #include "htmlediting.h" namespace WebCore { -EditCommand::EditCommand(Document& document) +String inputTypeNameForEditingAction(EditAction action) +{ + switch (action) { + case EditActionJustify: + case EditActionAlignLeft: + return ASCIILiteral("formatJustifyLeft"); + case EditActionAlignRight: + return ASCIILiteral("formatJustifyRight"); + case EditActionCenter: + return ASCIILiteral("formatJustifyCenter"); + case EditActionSubscript: + return ASCIILiteral("formatSubscript"); + case EditActionSuperscript: + return ASCIILiteral("formatSuperscript"); + case EditActionUnderline: + return ASCIILiteral("formatUnderline"); + case EditActionSetColor: + return ASCIILiteral("formatForeColor"); + case EditActionDeleteByDrag: + return ASCIILiteral("deleteByDrag"); + case EditActionCut: + return ASCIILiteral("deleteByCut"); + case EditActionBold: + return ASCIILiteral("formatBold"); + case EditActionItalics: + return ASCIILiteral("formatItalic"); + case EditActionPaste: + return ASCIILiteral("insertFromPaste"); + case EditActionDelete: + case EditActionTypingDeleteSelection: + return ASCIILiteral("deleteContent"); + case EditActionTypingDeleteBackward: + return ASCIILiteral("deleteContentBackward"); + case EditActionTypingDeleteForward: + return ASCIILiteral("deleteContentForward"); + case EditActionTypingDeleteWordBackward: + return ASCIILiteral("deleteWordBackward"); + case EditActionTypingDeleteWordForward: + return ASCIILiteral("deleteWordForward"); + case EditActionTypingDeleteLineBackward: + return ASCIILiteral("deleteHardLineBackward"); + case EditActionTypingDeleteLineForward: + return ASCIILiteral("deleteHardLineForward"); + case EditActionTypingDeletePendingComposition: + return ASCIILiteral("deleteCompositionText"); + case EditActionTypingDeleteFinalComposition: + return ASCIILiteral("deleteByComposition"); + case EditActionInsert: + case EditActionTypingInsertText: + return ASCIILiteral("insertText"); + case EditActionInsertReplacement: + return ASCIILiteral("insertReplacementText"); + case EditActionInsertFromDrop: + return ASCIILiteral("insertFromDrop"); + case EditActionTypingInsertLineBreak: + return ASCIILiteral("insertLineBreak"); + case EditActionTypingInsertParagraph: + return ASCIILiteral("insertParagraph"); + case EditActionInsertOrderedList: + return ASCIILiteral("insertOrderedList"); + case EditActionInsertUnorderedList: + return ASCIILiteral("insertUnorderedList"); + case EditActionTypingInsertPendingComposition: + return ASCIILiteral("insertCompositionText"); + case EditActionTypingInsertFinalComposition: + return ASCIILiteral("insertFromComposition"); + case EditActionIndent: + return ASCIILiteral("formatIndent"); + case EditActionOutdent: + return ASCIILiteral("formatOutdent"); + case EditActionSetWritingDirection: + return ASCIILiteral("formatSetInlineTextDirection"); + default: + return emptyString(); + } +} + +EditCommand::EditCommand(Document& document, EditAction editingAction) : m_document(document) - , m_parent(0) + , m_editingAction(editingAction) { ASSERT(document.frame()); - setStartingSelection(m_document->frame()->editor().avoidIntersectionWithDeleteButtonController(m_document->frame()->selection().selection())); + setStartingSelection(m_document->frame()->selection().selection()); setEndingSelection(m_startingSelection); } EditCommand::EditCommand(Document& document, const VisibleSelection& startingSelection, const VisibleSelection& endingSelection) : m_document(document) - , m_parent(0) { ASSERT(document.frame()); setStartingSelection(startingSelection); @@ -65,9 +143,15 @@ Frame& EditCommand::frame() return *document().frame(); } +const Frame& EditCommand::frame() const +{ + ASSERT(document().frame()); + return *document().frame(); +} + EditAction EditCommand::editingAction() const { - return EditActionUnspecified; + return m_editingAction; } static inline EditCommandComposition* compositionIfPossible(EditCommand* command) @@ -77,13 +161,28 @@ static inline EditCommandComposition* compositionIfPossible(EditCommand* command return toCompositeEditCommand(command)->composition(); } +bool EditCommand::isEditingTextAreaOrTextInput() const +{ + auto* frame = m_document->frame(); + if (!frame) + return false; + + auto* container = frame->selection().selection().start().containerNode(); + if (!container) + return false; + + auto* ancestor = container->shadowHost(); + if (!ancestor) + return false; + + return is<HTMLTextAreaElement>(*ancestor) || (is<HTMLInputElement>(*ancestor) && downcast<HTMLInputElement>(*ancestor).isText()); +} + void EditCommand::setStartingSelection(const VisibleSelection& s) { for (EditCommand* cmd = this; ; cmd = cmd->m_parent) { - if (EditCommandComposition* composition = compositionIfPossible(cmd)) { - ASSERT(cmd->isTopLevelCommand()); + if (auto* composition = compositionIfPossible(cmd)) composition->setStartingSelection(s); - } cmd->m_startingSelection = s; if (!cmd->m_parent || cmd->m_parent->isFirstCommand(cmd)) break; @@ -93,10 +192,8 @@ void EditCommand::setStartingSelection(const VisibleSelection& s) void EditCommand::setEndingSelection(const VisibleSelection &s) { for (EditCommand* cmd = this; cmd; cmd = cmd->m_parent) { - if (EditCommandComposition* composition = compositionIfPossible(cmd)) { - ASSERT(cmd->isTopLevelCommand()); + if (auto* composition = compositionIfPossible(cmd)) composition->setEndingSelection(s); - } cmd->m_endingSelection = s; } } @@ -104,7 +201,6 @@ void EditCommand::setEndingSelection(const VisibleSelection &s) void EditCommand::setParent(CompositeEditCommand* parent) { ASSERT((parent && !m_parent) || (!parent && m_parent)); - ASSERT(!parent || !isCompositeEditCommand() || !toCompositeEditCommand(this)->composition()); m_parent = parent; if (parent) { m_startingSelection = parent->m_endingSelection; @@ -112,6 +208,31 @@ void EditCommand::setParent(CompositeEditCommand* parent) } } +void EditCommand::postTextStateChangeNotification(AXTextEditType type, const String& text) +{ + if (!AXObjectCache::accessibilityEnabled()) + return; + postTextStateChangeNotification(type, text, frame().selection().selection().start()); +} + +void EditCommand::postTextStateChangeNotification(AXTextEditType type, const String& text, const VisiblePosition& position) +{ + if (!AXObjectCache::accessibilityEnabled()) + return; + if (!text.length()) + return; + auto* cache = document().existingAXObjectCache(); + if (!cache) + return; + auto* node = highestEditableRoot(position.deepEquivalent(), HasEditableAXRole); + cache->postTextStateChangeNotification(node, type, text, position); +} + +SimpleEditCommand::SimpleEditCommand(Document& document, EditAction editingAction) + : EditCommand(document, editingAction) +{ +} + void SimpleEditCommand::doReapply() { doApply(); @@ -120,7 +241,7 @@ void SimpleEditCommand::doReapply() #ifndef NDEBUG void SimpleEditCommand::addNodeAndDescendants(Node* startNode, HashSet<Node*>& nodes) { - for (Node* node = startNode; node; node = NodeTraversal::next(node, startNode)) + for (Node* node = startNode; node; node = NodeTraversal::next(*node, startNode)) nodes.add(node); } #endif diff --git a/Source/WebCore/editing/EditCommand.h b/Source/WebCore/editing/EditCommand.h index c563e226b..a054589c2 100644 --- a/Source/WebCore/editing/EditCommand.h +++ b/Source/WebCore/editing/EditCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,9 +23,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef EditCommand_h -#define EditCommand_h +#pragma once +#include "AXTextStateChangeIntent.h" #include "EditAction.h" #include "VisibleSelection.h" @@ -40,6 +40,8 @@ class Document; class Element; class Frame; +String inputTypeNameForEditingAction(EditAction); + class EditCommand : public RefCounted<EditCommand> { public: virtual ~EditCommand(); @@ -51,10 +53,7 @@ public: const VisibleSelection& startingSelection() const { return m_startingSelection; } const VisibleSelection& endingSelection() const { return m_endingSelection; } -#if PLATFORM(IOS) - virtual bool isInsertTextCommand() const { return false; } -#endif - + virtual bool isInsertTextCommand() const { return false; } virtual bool isSimpleEditCommand() const { return false; } virtual bool isCompositeEditCommand() const { return false; } virtual bool isEditCommandComposition() const { return false; } @@ -63,20 +62,28 @@ public: virtual void doApply() = 0; protected: - explicit EditCommand(Document&); + explicit EditCommand(Document&, EditAction = EditActionUnspecified); EditCommand(Document&, const VisibleSelection&, const VisibleSelection&); + const Frame& frame() const; Frame& frame(); - Document& document() { return m_document.get(); } + const Document& document() const { return m_document; } + Document& document() { return m_document; } CompositeEditCommand* parent() const { return m_parent; } void setStartingSelection(const VisibleSelection&); - void setEndingSelection(const VisibleSelection&); + WEBCORE_EXPORT void setEndingSelection(const VisibleSelection&); + + bool isEditingTextAreaOrTextInput() const; + + void postTextStateChangeNotification(AXTextEditType, const String&); + void postTextStateChangeNotification(AXTextEditType, const String&, const VisiblePosition&); private: Ref<Document> m_document; VisibleSelection m_startingSelection; VisibleSelection m_endingSelection; - CompositeEditCommand* m_parent; + CompositeEditCommand* m_parent { nullptr }; + EditAction m_editingAction { EditActionUnspecified }; }; enum ShouldAssumeContentIsAlwaysEditable { @@ -94,14 +101,14 @@ public: #endif protected: - explicit SimpleEditCommand(Document& document) : EditCommand(document) { } + explicit SimpleEditCommand(Document&, EditAction = EditActionUnspecified); #ifndef NDEBUG void addNodeAndDescendants(Node*, HashSet<Node*>&); #endif private: - virtual bool isSimpleEditCommand() const override { return true; } + bool isSimpleEditCommand() const override { return true; } }; inline SimpleEditCommand* toSimpleEditCommand(EditCommand* command) @@ -112,5 +119,3 @@ inline SimpleEditCommand* toSimpleEditCommand(EditCommand* command) } } // namespace WebCore - -#endif // EditCommand_h diff --git a/Source/WebCore/editing/EditingAllInOne.cpp b/Source/WebCore/editing/EditingAllInOne.cpp new file mode 100644 index 000000000..781bf5867 --- /dev/null +++ b/Source/WebCore/editing/EditingAllInOne.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2010 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. + */ + +// 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> +#include <BreakBlockquoteCommand.cpp> +#include <CompositeEditCommand.cpp> +#include <CreateLinkCommand.cpp> +#include <DeleteFromTextNodeCommand.cpp> +#include <DeleteSelectionCommand.cpp> +#include <DictationAlternative.cpp> +#include <DictationCommand.cpp> +#include <EditCommand.cpp> +#include <EditingStyle.cpp> +#include <Editor.cpp> +#include <EditorCommand.cpp> +#include <FormatBlockCommand.cpp> +#include <FrameSelection.cpp> +#include <HTMLInterchange.cpp> +#include <IndentOutdentCommand.cpp> +#include <InsertIntoTextNodeCommand.cpp> +#include <InsertLineBreakCommand.cpp> +#include <InsertListCommand.cpp> +#include <InsertNodeBeforeCommand.cpp> +#include <InsertParagraphSeparatorCommand.cpp> +#include <InsertTextCommand.cpp> +#include <MarkupAccumulator.cpp> +#include <MergeIdenticalElementsCommand.cpp> +#include <ModifySelectionListLevel.cpp> +#include <MoveSelectionCommand.cpp> +#include <RemoveCSSPropertyCommand.cpp> +#include <RemoveFormatCommand.cpp> +#include <RemoveNodeCommand.cpp> +#include <RemoveNodePreservingChildrenCommand.cpp> +#include <RenderedPosition.cpp> +#include <ReplaceNodeWithSpanCommand.cpp> +#include <ReplaceRangeWithTextCommand.cpp> +#include <ReplaceSelectionCommand.cpp> +#include <SetNodeAttributeCommand.cpp> +#include <SetSelectionCommand.cpp> +#include <SimplifyMarkupCommand.cpp> +#include <SmartReplace.cpp> +#if USE(CF) +#include <SmartReplaceCF.cpp> +#endif +#include <SpellingCorrectionCommand.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> +#include <VisiblePosition.cpp> +#include <VisibleSelection.cpp> +#include <VisibleUnits.cpp> +#include <WrapContentsInDummySpanCommand.cpp> +#include <htmlediting.cpp> +#include <markup.cpp> diff --git a/Source/WebCore/editing/EditingBehavior.h b/Source/WebCore/editing/EditingBehavior.h index cff6caba8..27fb1f076 100644 --- a/Source/WebCore/editing/EditingBehavior.h +++ b/Source/WebCore/editing/EditingBehavior.h @@ -18,8 +18,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef EditingBehavior_h -#define EditingBehavior_h +#pragma once #include "EditingBehaviorTypes.h" @@ -46,22 +45,22 @@ public: // On Windows, selections should always be considered as directional, regardless if it is // mouse-based or keyboard-based. - bool shouldConsiderSelectionAsDirectional() const { return m_type != EditingMacBehavior; } + bool shouldConsiderSelectionAsDirectional() const { return m_type != EditingMacBehavior && m_type != EditingIOSBehavior; } // On Mac, when revealing a selection (for example as a result of a Find operation on the Browser), // content should be scrolled such that the selection gets certer aligned. - bool shouldCenterAlignWhenSelectionIsRevealed() const { return m_type == EditingMacBehavior; } + bool shouldCenterAlignWhenSelectionIsRevealed() const { return m_type == EditingMacBehavior || m_type == EditingIOSBehavior; } // On Mac, style is considered present when present at the beginning of selection. On other platforms, // style has to be present throughout the selection. - bool shouldToggleStyleBasedOnStartOfSelection() const { return m_type == EditingMacBehavior; } + bool shouldToggleStyleBasedOnStartOfSelection() const { return m_type == EditingMacBehavior || m_type == EditingIOSBehavior; } // Standard Mac behavior when extending to a boundary is grow the selection rather than leaving the base // in place and moving the extent. Matches NSTextView. - bool shouldAlwaysGrowSelectionWhenExtendingToBoundary() const { return m_type == EditingMacBehavior; } + bool shouldAlwaysGrowSelectionWhenExtendingToBoundary() const { return m_type == EditingMacBehavior || m_type == EditingIOSBehavior; } // On Mac, when processing a contextual click, the object being clicked upon should be selected. - bool shouldSelectOnContextualMenuClick() const { return m_type == EditingMacBehavior; } + bool shouldSelectOnContextualMenuClick() const { return m_type == EditingMacBehavior || m_type == EditingIOSBehavior; } // On Linux, should be able to get and insert spelling suggestions without selecting the misspelled word. bool shouldAllowSpellingSuggestionsWithoutSelection() const @@ -78,17 +77,24 @@ public: // On Mac, selecting backwards by word/line from the middle of a word/line, and then going // forward leaves the caret back in the middle with no selection, instead of directly selecting // to the other end of the line/word (Unix/Windows behavior). - bool shouldExtendSelectionByWordOrLineAcrossCaret() const { return m_type != EditingMacBehavior; } + bool shouldExtendSelectionByWordOrLineAcrossCaret() const { return m_type != EditingMacBehavior && m_type != EditingIOSBehavior; } // 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; } + // On iOS the last entered character in a secure filed is shown momentarily, removing and adding back the + // space when deleting password cause space been showed insecurely. + bool shouldRebalanceWhiteSpacesInSecureField() const { return m_type != EditingIOSBehavior; } + + bool shouldSelectBasedOnDictionaryLookup() const { return m_type == EditingMacBehavior; } + + // Linux and Windows always extend selections from the extent endpoint. + bool shouldAlwaysExtendSelectionFromExtentEndpoint() const { return m_type != EditingMacBehavior && m_type != EditingIOSBehavior; } + private: EditingBehaviorType m_type; }; } // namespace WebCore - -#endif // EditingBehavior_h diff --git a/Source/WebCore/editing/EditingBehaviorTypes.h b/Source/WebCore/editing/EditingBehaviorTypes.h index 11345da5d..63ff1d06f 100644 --- a/Source/WebCore/editing/EditingBehaviorTypes.h +++ b/Source/WebCore/editing/EditingBehaviorTypes.h @@ -18,8 +18,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef EditingBehaviorTypes_h -#define EditingBehaviorTypes_h +#pragma once namespace WebCore { @@ -39,9 +38,8 @@ namespace WebCore { enum EditingBehaviorType { EditingMacBehavior, EditingWindowsBehavior, - EditingUnixBehavior + EditingUnixBehavior, + EditingIOSBehavior }; } // WebCore namespace - -#endif // EditingBehaviorTypes_h diff --git a/Source/WebCore/editing/EditingBoundary.h b/Source/WebCore/editing/EditingBoundary.h index ece7e036d..4947c610e 100644 --- a/Source/WebCore/editing/EditingBoundary.h +++ b/Source/WebCore/editing/EditingBoundary.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef EditingBoundary_h -#define EditingBoundary_h +#pragma once namespace WebCore { @@ -39,6 +38,4 @@ enum EditableType { HasEditableAXRole }; -} - -#endif // EditingBoundary_h +} // namespace WebCore diff --git a/Source/WebCore/editing/EditingStyle.cpp b/Source/WebCore/editing/EditingStyle.cpp index 364ff8054..be2f6fbbf 100644 --- a/Source/WebCore/editing/EditingStyle.cpp +++ b/Source/WebCore/editing/EditingStyle.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007, 2008, 2009, 2013 Apple Computer, Inc. + * Copyright (C) 2007, 2008, 2009, 2013 Apple Inc. * Copyright (C) 2010, 2011 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,6 +39,7 @@ #include "HTMLFontElement.h" #include "HTMLInterchange.h" #include "HTMLNames.h" +#include "HTMLSpanElement.h" #include "Node.h" #include "NodeTraversal.h" #include "QualifiedName.h" @@ -60,10 +61,9 @@ static const CSSPropertyID editingProperties[] = { CSSPropertyFontFamily, CSSPropertyFontSize, CSSPropertyFontStyle, - CSSPropertyFontVariant, + CSSPropertyFontVariantCaps, CSSPropertyFontWeight, CSSPropertyLetterSpacing, - CSSPropertyLineHeight, CSSPropertyOrphans, CSSPropertyTextAlign, CSSPropertyTextIndent, @@ -71,13 +71,12 @@ static const CSSPropertyID editingProperties[] = { CSSPropertyWhiteSpace, CSSPropertyWidows, CSSPropertyWordSpacing, -#if PLATFORM(IOS) +#if ENABLE(TOUCH_EVENTS) CSSPropertyWebkitTapHighlightColor, - CSSPropertyWebkitCompositionFillColor, #endif CSSPropertyWebkitTextDecorationsInEffect, CSSPropertyWebkitTextFillColor, -#if ENABLE(IOS_TEXT_AUTOSIZING) +#if ENABLE(TEXT_AUTOSIZING) CSSPropertyWebkitTextSizeAdjust, #endif CSSPropertyWebkitTextStrokeColor, @@ -102,8 +101,8 @@ static PassRefPtr<MutableStyleProperties> copyEditingProperties(StyleDeclaration static inline bool isEditingProperty(int id) { - for (size_t i = 0; i < WTF_ARRAY_LENGTH(editingProperties); ++i) { - if (editingProperties[i] == id) + for (auto& editingProperty : editingProperties) { + if (editingProperty == id) return true; } return false; @@ -129,59 +128,53 @@ static PassRefPtr<MutableStyleProperties> copyPropertiesFromComputedStyle(Node* return copyPropertiesFromComputedStyle(computedStyle, propertiesToInclude); } -static PassRefPtr<CSSValue> extractPropertyValue(const StyleProperties* style, CSSPropertyID propertyID) +static PassRefPtr<CSSValue> extractPropertyValue(const StyleProperties& style, CSSPropertyID propertyID) { - return style ? style->getPropertyCSSValue(propertyID) : PassRefPtr<CSSValue>(); + return style.getPropertyCSSValue(propertyID); } -static PassRefPtr<CSSValue> extractPropertyValue(ComputedStyleExtractor* computedStyle, CSSPropertyID propertyID) +static PassRefPtr<CSSValue> extractPropertyValue(ComputedStyleExtractor& computedStyle, CSSPropertyID propertyID) { - return computedStyle->propertyValue(propertyID); + return computedStyle.propertyValue(propertyID); } template<typename T> -int identifierForStyleProperty(T* style, CSSPropertyID propertyID) +int identifierForStyleProperty(T& style, CSSPropertyID propertyID) { RefPtr<CSSValue> value = extractPropertyValue(style, propertyID); - if (!value || !value->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(value.get())) return 0; - return toCSSPrimitiveValue(value.get())->getValueID(); + return downcast<CSSPrimitiveValue>(*value).valueID(); } -template<typename T> PassRefPtr<MutableStyleProperties> getPropertiesNotIn(StyleProperties* styleWithRedundantProperties, T* baseStyle); +template<typename T> PassRefPtr<MutableStyleProperties> getPropertiesNotIn(StyleProperties& styleWithRedundantProperties, T& baseStyle); enum LegacyFontSizeMode { AlwaysUseLegacyFontSize, UseLegacyFontSizeOnlyIfPixelValuesMatch }; static int legacyFontSizeFromCSSValue(Document*, CSSPrimitiveValue*, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode); -static bool isTransparentColorValue(CSSValue*); static bool hasTransparentBackgroundColor(StyleProperties*); -static PassRefPtr<CSSValue> backgroundColorInEffect(Node*); +static RefPtr<CSSValue> backgroundColorInEffect(Node*); class HTMLElementEquivalent { WTF_MAKE_FAST_ALLOCATED; public: - static PassOwnPtr<HTMLElementEquivalent> create(CSSPropertyID propertyID, CSSValueID primitiveValue, const QualifiedName& tagName) - { - return adoptPtr(new HTMLElementEquivalent(propertyID, primitiveValue, tagName)); - } + HTMLElementEquivalent(CSSPropertyID, CSSValueID primitiveValue, const QualifiedName& tagName); virtual ~HTMLElementEquivalent() { } - virtual bool matches(const Element* element) const { return !m_tagName || element->hasTagName(*m_tagName); } + virtual bool matches(const Element& element) const { return !m_tagName || element.hasTagName(*m_tagName); } virtual bool hasAttribute() const { return false; } - virtual bool propertyExistsInStyle(const StyleProperties* style) const { return style->getPropertyCSSValue(m_propertyID); } - virtual bool valueIsPresentInStyle(Element*, StyleProperties*) const; + virtual bool propertyExistsInStyle(const EditingStyle& style) const { return style.m_mutableStyle && style.m_mutableStyle->getPropertyCSSValue(m_propertyID); } + virtual bool valueIsPresentInStyle(Element&, const EditingStyle&) const; virtual void addToStyle(Element*, EditingStyle*) const; protected: HTMLElementEquivalent(CSSPropertyID); HTMLElementEquivalent(CSSPropertyID, const QualifiedName& tagName); - HTMLElementEquivalent(CSSPropertyID, CSSValueID primitiveValue, const QualifiedName& tagName); 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. + const QualifiedName* m_tagName { nullptr }; // We can store a pointer because HTML tag names are const global. }; HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id) : m_propertyID(id) - , m_tagName(0) { } @@ -199,10 +192,10 @@ HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, CSSValueID primit ASSERT(primitiveValue != CSSValueInvalid); } -bool HTMLElementEquivalent::valueIsPresentInStyle(Element* element, StyleProperties* style) const +bool HTMLElementEquivalent::valueIsPresentInStyle(Element& element, const EditingStyle& style) const { - RefPtr<CSSValue> value = style->getPropertyCSSValue(m_propertyID); - return matches(element) && value && value->isPrimitiveValue() && toCSSPrimitiveValue(value.get())->getValueID() == m_primitiveValue->getValueID(); + RefPtr<CSSValue> value = style.m_mutableStyle->getPropertyCSSValue(m_propertyID); + return matches(element) && is<CSSPrimitiveValue>(value.get()) && downcast<CSSPrimitiveValue>(*value).valueID() == m_primitiveValue->valueID(); } void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const @@ -212,57 +205,60 @@ void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const class HTMLTextDecorationEquivalent : public HTMLElementEquivalent { public: - static PassOwnPtr<HTMLElementEquivalent> create(CSSValueID primitiveValue, const QualifiedName& tagName) + HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const QualifiedName& tagName) + : HTMLElementEquivalent(CSSPropertyTextDecoration, primitiveValue, tagName) + , m_isUnderline(primitiveValue == CSSValueUnderline) { - return adoptPtr(new HTMLTextDecorationEquivalent(primitiveValue, tagName)); } - virtual bool propertyExistsInStyle(const StyleProperties*) const; - virtual bool valueIsPresentInStyle(Element*, StyleProperties*) const; - -private: - HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const QualifiedName& tagName); -}; -HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const QualifiedName& tagName) - : HTMLElementEquivalent(CSSPropertyTextDecoration, primitiveValue, tagName) - // m_propertyID is used in HTMLElementEquivalent::addToStyle -{ -} + bool propertyExistsInStyle(const EditingStyle& style) const override + { + if (changeInStyle(style) != TextDecorationChange::None) + return true; -bool HTMLTextDecorationEquivalent::propertyExistsInStyle(const StyleProperties* style) const -{ - return style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect) || style->getPropertyCSSValue(CSSPropertyTextDecoration); -} + if (!style.m_mutableStyle) + return false; -bool HTMLTextDecorationEquivalent::valueIsPresentInStyle(Element* element, StyleProperties* style) const -{ - RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); - if (!styleValue) - styleValue = style->getPropertyCSSValue(CSSPropertyTextDecoration); - return matches(element) && styleValue && styleValue->isValueList() && toCSSValueList(styleValue.get())->hasValue(m_primitiveValue.get()); -} + auto& mutableStyle = *style.m_mutableStyle; + return mutableStyle.getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect) + || mutableStyle.getPropertyCSSValue(CSSPropertyTextDecoration); + } -class HTMLAttributeEquivalent : public HTMLElementEquivalent { -public: - static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& tagName, const QualifiedName& attrName) + bool valueIsPresentInStyle(Element& element, const EditingStyle& style) const override { - return adoptPtr(new HTMLAttributeEquivalent(propertyID, tagName, attrName)); + if (!matches(element)) + return false; + auto change = changeInStyle(style); + if (change != TextDecorationChange::None) + return change == TextDecorationChange::Add; + RefPtr<CSSValue> styleValue = style.m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); + if (!styleValue) + styleValue = style.m_mutableStyle->getPropertyCSSValue(CSSPropertyTextDecoration); + return is<CSSValueList>(styleValue.get()) && downcast<CSSValueList>(*styleValue).hasValue(m_primitiveValue.get()); } - static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& attrName) + +private: + TextDecorationChange changeInStyle(const EditingStyle& style) const { - return adoptPtr(new HTMLAttributeEquivalent(propertyID, attrName)); + return m_isUnderline ? style.underlineChange() : style.strikeThroughChange(); } - bool matches(const Element* elem) const { return HTMLElementEquivalent::matches(elem) && elem->hasAttribute(m_attrName); } - virtual bool hasAttribute() const { return true; } - virtual bool valueIsPresentInStyle(Element*, StyleProperties*) const; - virtual void addToStyle(Element*, EditingStyle*) const; + bool m_isUnderline; +}; + +class HTMLAttributeEquivalent : public HTMLElementEquivalent { +public: + HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& tagName, const QualifiedName& attrName); + HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName); + + bool matches(const Element& element) const override { return HTMLElementEquivalent::matches(element) && element.hasAttribute(m_attrName); } + bool hasAttribute() const override { return true; } + bool valueIsPresentInStyle(Element&, const EditingStyle&) const override; + void addToStyle(Element*, EditingStyle*) const override; virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const; inline const QualifiedName& attributeName() const { return m_attrName; } protected: - HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& tagName, const QualifiedName& attrName); - HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName); const QualifiedName& m_attrName; // We can store a reference because HTML attribute names are const global. }; @@ -278,10 +274,10 @@ HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const Qualifi { } -bool HTMLAttributeEquivalent::valueIsPresentInStyle(Element* element, StyleProperties* style) const +bool HTMLAttributeEquivalent::valueIsPresentInStyle(Element& element, const EditingStyle& style) const { - RefPtr<CSSValue> value = attributeValueAsCSSValue(element); - RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(m_propertyID); + RefPtr<CSSValue> value = attributeValueAsCSSValue(&element); + RefPtr<CSSValue> styleValue = style.m_mutableStyle->getPropertyCSSValue(m_propertyID); return compareCSSValuePtr(value, styleValue); } @@ -295,25 +291,21 @@ void HTMLAttributeEquivalent::addToStyle(Element* element, EditingStyle* style) PassRefPtr<CSSValue> HTMLAttributeEquivalent::attributeValueAsCSSValue(Element* element) const { ASSERT(element); - if (!element->hasAttribute(m_attrName)) - return 0; + const AtomicString& value = element->getAttribute(m_attrName); + if (value.isNull()) + return nullptr; RefPtr<MutableStyleProperties> dummyStyle; dummyStyle = MutableStyleProperties::create(); - dummyStyle->setProperty(m_propertyID, element->getAttribute(m_attrName)); + dummyStyle->setProperty(m_propertyID, value); return dummyStyle->getPropertyCSSValue(m_propertyID); } class HTMLFontSizeEquivalent : public HTMLAttributeEquivalent { public: - static PassOwnPtr<HTMLFontSizeEquivalent> create() - { - return adoptPtr(new HTMLFontSizeEquivalent()); - } - virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const; - -private: HTMLFontSizeEquivalent(); + + PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const override; }; HTMLFontSizeEquivalent::HTMLFontSizeEquivalent() @@ -324,11 +316,12 @@ HTMLFontSizeEquivalent::HTMLFontSizeEquivalent() PassRefPtr<CSSValue> HTMLFontSizeEquivalent::attributeValueAsCSSValue(Element* element) const { ASSERT(element); - if (!element->hasAttribute(m_attrName)) - return 0; + const AtomicString& value = element->getAttribute(m_attrName); + if (value.isNull()) + return nullptr; CSSValueID size; - if (!HTMLFontElement::cssValueFromFontSizeNumber(element->getAttribute(m_attrName), size)) - return 0; + if (!HTMLFontElement::cssValueFromFontSizeNumber(value, size)) + return nullptr; return CSSPrimitiveValue::createIdentifier(size); } @@ -336,27 +329,33 @@ float EditingStyle::NoFontDelta = 0.0f; EditingStyle::EditingStyle() : m_shouldUseFixedDefaultFontSize(false) - , m_fontSizeDelta(NoFontDelta) + , m_underlineChange(static_cast<unsigned>(TextDecorationChange::None)) + , m_strikeThroughChange(static_cast<unsigned>(TextDecorationChange::None)) { } EditingStyle::EditingStyle(Node* node, PropertiesToInclude propertiesToInclude) - : m_shouldUseFixedDefaultFontSize(false) - , m_fontSizeDelta(NoFontDelta) + : EditingStyle() { init(node, propertiesToInclude); } EditingStyle::EditingStyle(const Position& position, PropertiesToInclude propertiesToInclude) - : m_shouldUseFixedDefaultFontSize(false) - , m_fontSizeDelta(NoFontDelta) + : EditingStyle() { init(position.deprecatedNode(), propertiesToInclude); } +EditingStyle::EditingStyle(const CSSStyleDeclaration* style) + : EditingStyle() +{ + if (style) + m_mutableStyle = style->copyProperties(); + extractFontSizeDelta(); +} + EditingStyle::EditingStyle(const StyleProperties* style) - : m_shouldUseFixedDefaultFontSize(false) - , m_fontSizeDelta(NoFontDelta) + : EditingStyle() { if (style) m_mutableStyle = style->mutableCopy(); @@ -364,46 +363,51 @@ EditingStyle::EditingStyle(const StyleProperties* style) } EditingStyle::EditingStyle(CSSPropertyID propertyID, const String& value) - : m_mutableStyle(0) - , m_shouldUseFixedDefaultFontSize(false) - , m_fontSizeDelta(NoFontDelta) + : EditingStyle() { setProperty(propertyID, value); + extractFontSizeDelta(); +} + +EditingStyle::EditingStyle(CSSPropertyID propertyID, CSSValueID value) + : EditingStyle() +{ + m_mutableStyle = MutableStyleProperties::create(); + m_mutableStyle->setProperty(propertyID, value); + extractFontSizeDelta(); } EditingStyle::~EditingStyle() { } -static RGBA32 cssValueToRGBA(CSSValue* colorValue) +static Color cssValueToColor(CSSValue* colorValue) { - if (!colorValue || !colorValue->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(colorValue)) return Color::transparent; - CSSPrimitiveValue* primitiveColor = toCSSPrimitiveValue(colorValue); - if (primitiveColor->isRGBColor()) - return primitiveColor->getRGBA32Value(); + CSSPrimitiveValue& primitiveColor = downcast<CSSPrimitiveValue>(*colorValue); + if (primitiveColor.isRGBColor()) + return primitiveColor.color(); - RGBA32 rgba = 0; - CSSParser::parseColor(rgba, colorValue->cssText()); - return rgba; + return CSSParser::parseColor(colorValue->cssText()); } template<typename T> -static inline RGBA32 textColorFromStyle(T* style) +static inline Color textColorFromStyle(T& style) { - return cssValueToRGBA(extractPropertyValue(style, CSSPropertyColor).get()); + return cssValueToColor(extractPropertyValue(style, CSSPropertyColor).get()); } template<typename T> -static inline RGBA32 backgroundColorFromStyle(T* style) +static inline Color backgroundColorFromStyle(T& style) { - return cssValueToRGBA(extractPropertyValue(style, CSSPropertyBackgroundColor).get()); + return cssValueToColor(extractPropertyValue(style, CSSPropertyBackgroundColor).get()); } -static inline RGBA32 rgbaBackgroundColorInEffect(Node* node) +static inline Color rgbaBackgroundColorInEffect(Node* node) { - return cssValueToRGBA(backgroundColorInEffect(node).get()); + return cssValueToColor(backgroundColorInEffect(node).get()); } static int textAlignResolvingStartAndEnd(int textAlign, int direction) @@ -429,7 +433,7 @@ static int textAlignResolvingStartAndEnd(int textAlign, int direction) } template<typename T> -static int textAlignResolvingStartAndEnd(T* style) +static int textAlignResolvingStartAndEnd(T& style) { return textAlignResolvingStartAndEnd(identifierForStyleProperty(style, CSSPropertyTextAlign), identifierForStyleProperty(style, CSSPropertyDirection)); } @@ -455,7 +459,7 @@ void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude) } if (node && node->computedStyle()) { - RenderStyle* renderStyle = node->computedStyle(); + auto* renderStyle = node->computedStyle(); removeTextFillAndStrokeColorsIfNeeded(renderStyle); if (renderStyle->fontDescription().keywordSize()) m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyleAtPosition.getFontSizeCSSValuePreferringKeyword()->cssText()); @@ -465,7 +469,7 @@ void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude) extractFontSizeDelta(); } -void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle) +void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(const RenderStyle* renderStyle) { // If a node's text fill color is invalid, then its children use // their font-color as their text fill color (they don't @@ -497,23 +501,46 @@ void EditingStyle::extractFontSizeDelta() // Get the adjustment amount out of the style. RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta); - if (!value || !value->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(value.get())) return; - CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(value.get()); + CSSPrimitiveValue& primitiveValue = downcast<CSSPrimitiveValue>(*value); // Only PX handled now. If we handle more types in the future, perhaps // a switch statement here would be more appropriate. - if (!primitiveValue->isPx()) + if (!primitiveValue.isPx()) return; - m_fontSizeDelta = primitiveValue->getFloatValue(); + m_fontSizeDelta = primitiveValue.floatValue(); m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta); } bool EditingStyle::isEmpty() const { - return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta; + return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta + && underlineChange() == TextDecorationChange::None && strikeThroughChange() == TextDecorationChange::None; +} + +Ref<MutableStyleProperties> EditingStyle::styleWithResolvedTextDecorations() const +{ + bool hasTextDecorationChanges = underlineChange() != TextDecorationChange::None || strikeThroughChange() != TextDecorationChange::None; + if (m_mutableStyle && !hasTextDecorationChanges) + return *m_mutableStyle; + + Ref<MutableStyleProperties> style = m_mutableStyle ? m_mutableStyle->mutableCopy() : MutableStyleProperties::create(); + + Ref<CSSValueList> valueList = CSSValueList::createSpaceSeparated(); + if (underlineChange() == TextDecorationChange::Add) + valueList->append(CSSValuePool::singleton().createIdentifierValue(CSSValueUnderline)); + if (strikeThroughChange() == TextDecorationChange::Add) + valueList->append(CSSValuePool::singleton().createIdentifierValue(CSSValueLineThrough)); + + if (valueList->length()) + style->setProperty(CSSPropertyTextDecoration, valueList.ptr()); + else + style->setProperty(CSSPropertyTextDecoration, CSSValuePool::singleton().createIdentifierValue(CSSValueNone)); + + return style; } bool EditingStyle::textDirection(WritingDirection& writingDirection) const @@ -522,16 +549,16 @@ bool EditingStyle::textDirection(WritingDirection& writingDirection) const return false; RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); - if (!unicodeBidi || !unicodeBidi->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(unicodeBidi.get())) return false; - CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID(); + CSSValueID unicodeBidiValue = downcast<CSSPrimitiveValue>(*unicodeBidi).valueID(); if (unicodeBidiValue == CSSValueEmbed) { RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection); - if (!direction || !direction->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(direction.get())) return false; - writingDirection = toCSSPrimitiveValue(direction.get())->getValueID() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection; + writingDirection = downcast<CSSPrimitiveValue>(*direction).valueID() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection; return true; } @@ -558,11 +585,62 @@ void EditingStyle::overrideWithStyle(const StyleProperties* style) return mergeStyle(style, OverrideValues); } +static void applyTextDecorationChangeToValueList(CSSValueList& valueList, TextDecorationChange change, Ref<CSSPrimitiveValue>&& value) +{ + switch (change) { + case TextDecorationChange::None: + break; + case TextDecorationChange::Add: + valueList.append(WTFMove(value)); + break; + case TextDecorationChange::Remove: + valueList.removeAll(&value.get()); + break; + } +} + +void EditingStyle::overrideTypingStyleAt(const EditingStyle& style, const Position& position) +{ + mergeStyle(style.m_mutableStyle.get(), OverrideValues); + + m_fontSizeDelta += style.m_fontSizeDelta; + + prepareToApplyAt(position, EditingStyle::PreserveWritingDirection); + + auto underlineChange = style.underlineChange(); + auto strikeThroughChange = style.strikeThroughChange(); + if (underlineChange == TextDecorationChange::None && strikeThroughChange == TextDecorationChange::None) + return; + + if (!m_mutableStyle) + m_mutableStyle = MutableStyleProperties::create(); + + auto& cssValuePool = CSSValuePool::singleton(); + Ref<CSSPrimitiveValue> underline = cssValuePool.createIdentifierValue(CSSValueUnderline); + Ref<CSSPrimitiveValue> lineThrough = cssValuePool.createIdentifierValue(CSSValueLineThrough); + RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); + RefPtr<CSSValueList> valueList; + if (value && value->isValueList()) { + valueList = downcast<CSSValueList>(*value).copy(); + applyTextDecorationChangeToValueList(*valueList, underlineChange, WTFMove(underline)); + applyTextDecorationChangeToValueList(*valueList, strikeThroughChange, WTFMove(lineThrough)); + } else { + valueList = CSSValueList::createSpaceSeparated(); + if (underlineChange == TextDecorationChange::Add) + valueList->append(WTFMove(underline)); + if (strikeThroughChange == TextDecorationChange::Add) + valueList->append(WTFMove(lineThrough)); + } + m_mutableStyle->setProperty(CSSPropertyWebkitTextDecorationsInEffect, valueList.get()); +} + void EditingStyle::clear() { - m_mutableStyle.clear(); + m_mutableStyle = nullptr; m_shouldUseFixedDefaultFontSize = false; m_fontSizeDelta = NoFontDelta; + setUnderlineChange(TextDecorationChange::None); + setStrikeThroughChange(TextDecorationChange::None); } PassRefPtr<EditingStyle> EditingStyle::copy() const @@ -571,6 +649,8 @@ PassRefPtr<EditingStyle> EditingStyle::copy() const if (m_mutableStyle) copy->m_mutableStyle = m_mutableStyle->mutableCopy(); copy->m_shouldUseFixedDefaultFontSize = m_shouldUseFixedDefaultFontSize; + copy->m_underlineChange = m_underlineChange; + copy->m_strikeThroughChange = m_strikeThroughChange; copy->m_fontSizeDelta = m_fontSizeDelta; return copy; } @@ -615,8 +695,8 @@ void EditingStyle::removeStyleAddedByNode(Node* node) return; RefPtr<MutableStyleProperties> parentStyle = copyPropertiesFromComputedStyle(node->parentNode(), EditingPropertiesInEffect); RefPtr<MutableStyleProperties> nodeStyle = copyPropertiesFromComputedStyle(node, EditingPropertiesInEffect); - nodeStyle->removeEquivalentProperties(parentStyle.get()); - m_mutableStyle->removeEquivalentProperties(nodeStyle.get()); + removeEquivalentProperties(*parentStyle); + removeEquivalentProperties(*nodeStyle); } void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node) @@ -625,12 +705,13 @@ void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node) return; RefPtr<MutableStyleProperties> parentStyle = copyPropertiesFromComputedStyle(node->parentNode(), EditingPropertiesInEffect); - RefPtr<MutableStyleProperties> nodeStyle = copyPropertiesFromComputedStyle(node, EditingPropertiesInEffect); - nodeStyle->removeEquivalentProperties(parentStyle.get()); + RefPtr<EditingStyle> nodeStyle = EditingStyle::create(node, EditingPropertiesInEffect); + nodeStyle->removeEquivalentProperties(*parentStyle); - unsigned propertyCount = nodeStyle->propertyCount(); + MutableStyleProperties* style = nodeStyle->style(); + unsigned propertyCount = style->propertyCount(); for (unsigned i = 0; i < propertyCount; ++i) - m_mutableStyle->removeProperty(nodeStyle->propertyAt(i).id()); + m_mutableStyle->removeProperty(style->propertyAt(i).id()); } void EditingStyle::collapseTextDecorationProperties() @@ -662,13 +743,16 @@ TriState EditingStyle::triStateOfStyle(EditingStyle* style) const { if (!style || !style->m_mutableStyle) return FalseTriState; - return triStateOfStyle(style->m_mutableStyle.get(), DoNotIgnoreTextOnlyProperties); + return triStateOfStyle(*style->m_mutableStyle, DoNotIgnoreTextOnlyProperties); } template<typename T> -TriState EditingStyle::triStateOfStyle(T* styleToCompare, ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const +TriState EditingStyle::triStateOfStyle(T& styleToCompare, ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const { - RefPtr<MutableStyleProperties> difference = getPropertiesNotIn(m_mutableStyle.get(), styleToCompare); + if (!m_mutableStyle) + return TrueTriState; + + RefPtr<MutableStyleProperties> difference = getPropertiesNotIn(*m_mutableStyle, styleToCompare); if (shouldIgnoreTextOnlyProperties == IgnoreTextOnlyProperties) difference->removePropertiesInSet(textOnlyProperties, WTF_ARRAY_LENGTH(textOnlyProperties)); @@ -691,10 +775,10 @@ TriState EditingStyle::triStateOfStyle(const VisibleSelection& selection) const TriState state = FalseTriState; bool nodeIsStart = true; - for (Node* node = selection.start().deprecatedNode(); node; node = NodeTraversal::next(node)) { + for (Node* node = selection.start().deprecatedNode(); node; node = NodeTraversal::next(*node)) { if (node->renderer() && node->hasEditableStyle()) { ComputedStyleExtractor computedStyle(node); - TriState nodeState = triStateOfStyle(&computedStyle, node->isTextNode() ? EditingStyle::DoNotIgnoreTextOnlyProperties : EditingStyle::IgnoreTextOnlyProperties); + TriState nodeState = triStateOfStyle(computedStyle, node->isTextNode() ? EditingStyle::DoNotIgnoreTextOnlyProperties : EditingStyle::IgnoreTextOnlyProperties); if (nodeIsStart) { state = nodeState; nodeIsStart = false; @@ -711,16 +795,67 @@ TriState EditingStyle::triStateOfStyle(const VisibleSelection& selection) const return state; } -bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, EditingStyle* extractedStyle, Vector<CSSPropertyID>* conflictingProperties) const +static RefPtr<CSSValueList> textDecorationValueList(const StyleProperties& properties) +{ + RefPtr<CSSValue> value = properties.getPropertyCSSValue(CSSPropertyTextDecoration); + if (!is<CSSValueList>(value.get())) + return nullptr; + return downcast<CSSValueList>(value.get()); +} + +bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, RefPtr<MutableStyleProperties>* newInlineStylePtr, EditingStyle* extractedStyle) const { ASSERT(element); - ASSERT(!conflictingProperties || conflictingProperties->isEmpty()); const StyleProperties* inlineStyle = element->inlineStyle(); - if (!m_mutableStyle || !inlineStyle) + if (!inlineStyle) return false; + bool conflicts = false; + RefPtr<MutableStyleProperties> newInlineStyle; + if (newInlineStylePtr) { + newInlineStyle = inlineStyle->mutableCopy(); + *newInlineStylePtr = newInlineStyle; + } + + bool shouldRemoveUnderline = underlineChange() == TextDecorationChange::Remove; + bool shouldRemoveStrikeThrough = strikeThroughChange() == TextDecorationChange::Remove; + if (shouldRemoveUnderline || shouldRemoveStrikeThrough) { + if (RefPtr<CSSValueList> valueList = textDecorationValueList(*inlineStyle)) { + RefPtr<CSSValueList> newValueList = valueList->copy(); + RefPtr<CSSValueList> extractedValueList = CSSValueList::createSpaceSeparated(); + + Ref<CSSPrimitiveValue> underline = CSSValuePool::singleton().createIdentifierValue(CSSValueUnderline); + if (shouldRemoveUnderline && valueList->hasValue(underline.ptr())) { + if (!newInlineStyle) + return true; + newValueList->removeAll(underline.ptr()); + extractedValueList->append(WTFMove(underline)); + } - unsigned propertyCount = m_mutableStyle->propertyCount(); + Ref<CSSPrimitiveValue> lineThrough = CSSValuePool::singleton().createIdentifierValue(CSSValueLineThrough); + if (shouldRemoveStrikeThrough && valueList->hasValue(lineThrough.ptr())) { + if (!newInlineStyle) + return true; + newValueList->removeAll(lineThrough.ptr()); + extractedValueList->append(WTFMove(lineThrough)); + } + + if (extractedValueList->length()) { + conflicts = true; + if (newValueList->length()) + newInlineStyle->setProperty(CSSPropertyTextDecoration, newValueList); + else + newInlineStyle->removeProperty(CSSPropertyTextDecoration); + + if (extractedStyle) { + bool isImportant = inlineStyle->propertyIsImportant(CSSPropertyTextDecoration); + extractedStyle->setProperty(CSSPropertyTextDecoration, extractedValueList->cssText(), isImportant); + } + } + } + } + + unsigned propertyCount = m_mutableStyle ? m_mutableStyle->propertyCount() : 0; for (unsigned i = 0; i < propertyCount; ++i) { CSSPropertyID propertyID = m_mutableStyle->propertyAt(i).id(); @@ -729,52 +864,53 @@ bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, Edi continue; if (propertyID == CSSPropertyWebkitTextDecorationsInEffect && inlineStyle->getPropertyCSSValue(CSSPropertyTextDecoration)) { - if (!conflictingProperties) + if (!newInlineStyle) return true; - conflictingProperties->append(CSSPropertyTextDecoration); + conflicts = true; + newInlineStyle->removeProperty(CSSPropertyTextDecoration); if (extractedStyle) extractedStyle->setProperty(CSSPropertyTextDecoration, inlineStyle->getPropertyValue(CSSPropertyTextDecoration), inlineStyle->propertyIsImportant(CSSPropertyTextDecoration)); - continue; } if (!inlineStyle->getPropertyCSSValue(propertyID)) continue; if (propertyID == CSSPropertyUnicodeBidi && inlineStyle->getPropertyCSSValue(CSSPropertyDirection)) { - if (!conflictingProperties) + if (!newInlineStyle) return true; - conflictingProperties->append(CSSPropertyDirection); + conflicts = true; + newInlineStyle->removeProperty(CSSPropertyDirection); if (extractedStyle) extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID)); } - if (!conflictingProperties) + if (!newInlineStyle) return true; - conflictingProperties->append(propertyID); - + conflicts = true; + newInlineStyle->removeProperty(propertyID); if (extractedStyle) extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID)); } - return conflictingProperties && !conflictingProperties->isEmpty(); + return conflicts; } -static const Vector<OwnPtr<HTMLElementEquivalent>>& htmlElementEquivalents() +static const Vector<std::unique_ptr<HTMLElementEquivalent>>& htmlElementEquivalents() { - DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLElementEquivalent>>, HTMLElementEquivalents, ()); + static NeverDestroyed<Vector<std::unique_ptr<HTMLElementEquivalent>>> HTMLElementEquivalents; - if (!HTMLElementEquivalents.size()) { - HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag)); - HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag)); - HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag)); - HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag)); - HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag)); - HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::emTag)); + if (!HTMLElementEquivalents.get().size()) { + HTMLElementEquivalents.get().append(std::make_unique<HTMLElementEquivalent>(CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag)); + HTMLElementEquivalents.get().append(std::make_unique<HTMLElementEquivalent>(CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag)); + HTMLElementEquivalents.get().append(std::make_unique<HTMLElementEquivalent>(CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag)); + HTMLElementEquivalents.get().append(std::make_unique<HTMLElementEquivalent>(CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag)); + HTMLElementEquivalents.get().append(std::make_unique<HTMLElementEquivalent>(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag)); + HTMLElementEquivalents.get().append(std::make_unique<HTMLElementEquivalent>(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::emTag)); - HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueUnderline, HTMLNames::uTag)); - HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::sTag)); - HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::strikeTag)); + HTMLElementEquivalents.get().append(std::make_unique<HTMLTextDecorationEquivalent>(CSSValueUnderline, HTMLNames::uTag)); + HTMLElementEquivalents.get().append(std::make_unique<HTMLTextDecorationEquivalent>(CSSValueLineThrough, HTMLNames::sTag)); + HTMLElementEquivalents.get().append(std::make_unique<HTMLTextDecorationEquivalent>(CSSValueLineThrough, HTMLNames::strikeTag)); } return HTMLElementEquivalents; @@ -783,14 +919,13 @@ static const Vector<OwnPtr<HTMLElementEquivalent>>& htmlElementEquivalents() bool EditingStyle::conflictsWithImplicitStyleOfElement(HTMLElement* element, EditingStyle* extractedStyle, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const { - if (!m_mutableStyle) + if (isEmpty()) return false; - const Vector<OwnPtr<HTMLElementEquivalent>>& HTMLElementEquivalents = htmlElementEquivalents(); - for (size_t i = 0; i < HTMLElementEquivalents.size(); ++i) { - const HTMLElementEquivalent* equivalent = HTMLElementEquivalents[i].get(); - if (equivalent->matches(element) && equivalent->propertyExistsInStyle(m_mutableStyle.get()) - && (shouldExtractMatchingStyle == ExtractMatchingStyle || !equivalent->valueIsPresentInStyle(element, m_mutableStyle.get()))) { + const Vector<std::unique_ptr<HTMLElementEquivalent>>& HTMLElementEquivalents = htmlElementEquivalents(); + for (auto& equivalent : HTMLElementEquivalents) { + if (equivalent->matches(*element) && equivalent->propertyExistsInStyle(*this) + && (shouldExtractMatchingStyle == ExtractMatchingStyle || !equivalent->valueIsPresentInStyle(*element, *this))) { if (extractedStyle) equivalent->addToStyle(element, extractedStyle); return true; @@ -799,19 +934,19 @@ bool EditingStyle::conflictsWithImplicitStyleOfElement(HTMLElement* element, Edi return false; } -static const Vector<OwnPtr<HTMLAttributeEquivalent>>& htmlAttributeEquivalents() +static const Vector<std::unique_ptr<HTMLAttributeEquivalent>>& htmlAttributeEquivalents() { - DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLAttributeEquivalent>>, HTMLAttributeEquivalents, ()); + static NeverDestroyed<Vector<std::unique_ptr<HTMLAttributeEquivalent>>> HTMLAttributeEquivalents; - if (!HTMLAttributeEquivalents.size()) { + if (!HTMLAttributeEquivalents.get().size()) { // elementIsStyledSpanOrHTMLEquivalent depends on the fact each HTMLAttriuteEquivalent matches exactly one attribute // of exactly one element except dirAttr. - HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr)); - HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr)); - HTMLAttributeEquivalents.append(HTMLFontSizeEquivalent::create()); + HTMLAttributeEquivalents.get().append(std::make_unique<HTMLAttributeEquivalent>(CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr)); + HTMLAttributeEquivalents.get().append(std::make_unique<HTMLAttributeEquivalent>(CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr)); + HTMLAttributeEquivalents.get().append(std::make_unique<HTMLFontSizeEquivalent>()); - HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyDirection, HTMLNames::dirAttr)); - HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyUnicodeBidi, HTMLNames::dirAttr)); + HTMLAttributeEquivalents.get().append(std::make_unique<HTMLAttributeEquivalent>(CSSPropertyDirection, HTMLNames::dirAttr)); + HTMLAttributeEquivalents.get().append(std::make_unique<HTMLAttributeEquivalent>(CSSPropertyUnicodeBidi, HTMLNames::dirAttr)); } return HTMLAttributeEquivalents; @@ -820,13 +955,12 @@ static const Vector<OwnPtr<HTMLAttributeEquivalent>>& htmlAttributeEquivalents() bool EditingStyle::conflictsWithImplicitStyleOfAttributes(HTMLElement* element) const { ASSERT(element); - if (!m_mutableStyle) + if (isEmpty()) return false; - const Vector<OwnPtr<HTMLAttributeEquivalent>>& HTMLAttributeEquivalents = htmlAttributeEquivalents(); - for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) { - if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->propertyExistsInStyle(m_mutableStyle.get()) - && !HTMLAttributeEquivalents[i]->valueIsPresentInStyle(element, m_mutableStyle.get())) + const Vector<std::unique_ptr<HTMLAttributeEquivalent>>& HTMLAttributeEquivalents = htmlAttributeEquivalents(); + for (auto& equivalent : HTMLAttributeEquivalents) { + if (equivalent->matches(*element) && equivalent->propertyExistsInStyle(*this) && !equivalent->valueIsPresentInStyle(*element, *this)) return true; } @@ -842,17 +976,14 @@ bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement* elem if (!m_mutableStyle) return false; - const Vector<OwnPtr<HTMLAttributeEquivalent>>& HTMLAttributeEquivalents = htmlAttributeEquivalents(); bool removed = false; - for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) { - const HTMLAttributeEquivalent* equivalent = HTMLAttributeEquivalents[i].get(); - + for (auto& equivalent : htmlAttributeEquivalents()) { // unicode-bidi and direction are pushed down separately so don't push down with other styles. if (shouldPreserveWritingDirection == PreserveWritingDirection && equivalent->attributeName() == HTMLNames::dirAttr) continue; - if (!equivalent->matches(element) || !equivalent->propertyExistsInStyle(m_mutableStyle.get()) - || (shouldExtractMatchingStyle == DoNotExtractMatchingStyle && equivalent->valueIsPresentInStyle(element, m_mutableStyle.get()))) + if (!equivalent->matches(*element) || !equivalent->propertyExistsInStyle(*this) + || (shouldExtractMatchingStyle == DoNotExtractMatchingStyle && equivalent->valueIsPresentInStyle(*element, *this))) continue; if (extractedStyle) @@ -866,10 +997,28 @@ bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement* elem bool EditingStyle::styleIsPresentInComputedStyleOfNode(Node* node) const { - if (!m_mutableStyle) + if (isEmpty()) return true; ComputedStyleExtractor computedStyle(node); - return getPropertiesNotIn(m_mutableStyle.get(), &computedStyle)->isEmpty(); + + bool shouldAddUnderline = underlineChange() == TextDecorationChange::Add; + bool shouldAddLineThrough = strikeThroughChange() == TextDecorationChange::Add; + if (shouldAddUnderline || shouldAddLineThrough) { + bool hasUnderline = false; + bool hasLineThrough = false; + if (RefPtr<CSSValue> value = computedStyle.propertyValue(CSSPropertyTextDecoration)) { + if (value->isValueList()) { + auto& cssValuePool = CSSValuePool::singleton(); + const CSSValueList& valueList = downcast<CSSValueList>(*value); + hasUnderline = valueList.hasValue(cssValuePool.createIdentifierValue(CSSValueUnderline).ptr()); + hasLineThrough = valueList.hasValue(cssValuePool.createIdentifierValue(CSSValueLineThrough).ptr()); + } + } + if ((shouldAddUnderline && !hasUnderline) || (shouldAddLineThrough && !hasLineThrough)) + return false; + } + + return !m_mutableStyle || getPropertiesNotIn(*m_mutableStyle, computedStyle)->isEmpty(); } bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* element) @@ -878,10 +1027,8 @@ bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* elemen if (element->hasTagName(HTMLNames::spanTag)) elementIsSpanOrElementEquivalent = true; else { - const Vector<OwnPtr<HTMLElementEquivalent>>& HTMLElementEquivalents = htmlElementEquivalents(); - size_t i; - for (i = 0; i < HTMLElementEquivalents.size(); ++i) { - if (HTMLElementEquivalents[i]->matches(element)) { + for (auto& equivalent : htmlElementEquivalents()) { + if (equivalent->matches(*element)) { elementIsSpanOrElementEquivalent = true; break; } @@ -892,16 +1039,15 @@ bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* elemen return elementIsSpanOrElementEquivalent; // span, b, etc... without any attributes unsigned matchedAttributes = 0; - const Vector<OwnPtr<HTMLAttributeEquivalent>>& HTMLAttributeEquivalents = htmlAttributeEquivalents(); - for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) { - if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->attributeName() != HTMLNames::dirAttr) + for (auto& equivalent : htmlAttributeEquivalents()) { + if (equivalent->matches(*element) && equivalent->attributeName() != HTMLNames::dirAttr) matchedAttributes++; } if (!elementIsSpanOrElementEquivalent && !matchedAttributes) return false; // element is not a span, a html element equivalent, or font element. - if (element->getAttribute(HTMLNames::classAttr) == AppleStyleSpanClass) + if (element->attributeWithoutSynchronization(HTMLNames::classAttr) == AppleStyleSpanClass) matchedAttributes++; if (element->hasAttribute(HTMLNames::styleAttr)) { @@ -938,22 +1084,22 @@ void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWrit direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection); } - m_mutableStyle->removeEquivalentProperties(styleAtPosition); + removeEquivalentProperties(*styleAtPosition); - if (textAlignResolvingStartAndEnd(m_mutableStyle.get()) == textAlignResolvingStartAndEnd(styleAtPosition)) + if (textAlignResolvingStartAndEnd(*m_mutableStyle) == textAlignResolvingStartAndEnd(*styleAtPosition)) m_mutableStyle->removeProperty(CSSPropertyTextAlign); - if (textColorFromStyle(m_mutableStyle.get()) == textColorFromStyle(styleAtPosition)) + if (textColorFromStyle(*m_mutableStyle) == textColorFromStyle(*styleAtPosition)) m_mutableStyle->removeProperty(CSSPropertyColor); if (hasTransparentBackgroundColor(m_mutableStyle.get()) - || cssValueToRGBA(m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor).get()) == rgbaBackgroundColorInEffect(position.containerNode())) + || cssValueToColor(m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor).get()) == rgbaBackgroundColorInEffect(position.containerNode())) m_mutableStyle->removeProperty(CSSPropertyBackgroundColor); - if (unicodeBidi && unicodeBidi->isPrimitiveValue()) { - m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSValueID>(toCSSPrimitiveValue(unicodeBidi.get())->getValueID())); - if (direction && direction->isPrimitiveValue()) - m_mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSValueID>(toCSSPrimitiveValue(direction.get())->getValueID())); + if (is<CSSPrimitiveValue>(unicodeBidi.get())) { + m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSValueID>(downcast<CSSPrimitiveValue>(*unicodeBidi).valueID())); + if (is<CSSPrimitiveValue>(direction.get())) + m_mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSValueID>(downcast<CSSPrimitiveValue>(*direction).valueID())); } } @@ -986,10 +1132,14 @@ void EditingStyle::mergeInlineStyleOfElement(StyledElement* element, CSSProperty } static inline bool elementMatchesAndPropertyIsNotInInlineStyleDecl(const HTMLElementEquivalent* equivalent, const StyledElement* element, - EditingStyle::CSSPropertyOverrideMode mode, StyleProperties* style) + EditingStyle::CSSPropertyOverrideMode mode, EditingStyle& style) { - return equivalent->matches(element) && (!element->inlineStyle() || !equivalent->propertyExistsInStyle(element->inlineStyle())) - && (mode == EditingStyle::OverrideValues || !equivalent->propertyExistsInStyle(style)); + if (!equivalent->matches(*element)) + return false; + if (mode != EditingStyle::OverrideValues && equivalent->propertyExistsInStyle(style)) + return false; + + return !element->inlineStyle() || !equivalent->propertyExistsInStyle(EditingStyle::create(element->inlineStyle()).get()); } static PassRefPtr<MutableStyleProperties> extractEditingProperties(const StyleProperties* style, EditingStyle::PropertiesToInclude propertiesToInclude) @@ -1020,26 +1170,23 @@ void EditingStyle::mergeInlineAndImplicitStyleOfElement(StyledElement* element, styleFromRules->m_mutableStyle = extractEditingProperties(styleFromRules->m_mutableStyle.get(), propertiesToInclude); mergeStyle(styleFromRules->m_mutableStyle.get(), mode); - const Vector<OwnPtr<HTMLElementEquivalent>>& elementEquivalents = htmlElementEquivalents(); - for (size_t i = 0; i < elementEquivalents.size(); ++i) { - if (elementMatchesAndPropertyIsNotInInlineStyleDecl(elementEquivalents[i].get(), element, mode, m_mutableStyle.get())) - elementEquivalents[i]->addToStyle(element, this); + for (auto& equivalent : htmlElementEquivalents()) { + if (elementMatchesAndPropertyIsNotInInlineStyleDecl(equivalent.get(), element, mode, *this)) + equivalent->addToStyle(element, this); } - const Vector<OwnPtr<HTMLAttributeEquivalent>>& attributeEquivalents = htmlAttributeEquivalents(); - for (size_t i = 0; i < attributeEquivalents.size(); ++i) { - if (attributeEquivalents[i]->attributeName() == HTMLNames::dirAttr) + for (auto& equivalent : htmlAttributeEquivalents()) { + if (equivalent->attributeName() == HTMLNames::dirAttr) continue; // We don't want to include directionality - if (elementMatchesAndPropertyIsNotInInlineStyleDecl(attributeEquivalents[i].get(), element, mode, m_mutableStyle.get())) - attributeEquivalents[i]->addToStyle(element, this); + if (elementMatchesAndPropertyIsNotInInlineStyleDecl(equivalent.get(), element, mode, *this)) + equivalent->addToStyle(element, this); } } -PassRefPtr<EditingStyle> EditingStyle::wrappingStyleForSerialization(Node* context, bool shouldAnnotate) +Ref<EditingStyle> EditingStyle::wrappingStyleForSerialization(Node* context, bool shouldAnnotate) { - RefPtr<EditingStyle> wrappingStyle; if (shouldAnnotate) { - wrappingStyle = EditingStyle::create(context, EditingStyle::EditingPropertiesInEffect); + auto wrappingStyle = EditingStyle::create(context, EditingStyle::EditingPropertiesInEffect); // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote, // to help us differentiate those styles from ones that the user has applied. @@ -1049,33 +1196,34 @@ PassRefPtr<EditingStyle> EditingStyle::wrappingStyleForSerialization(Node* conte // Call collapseTextDecorationProperties first or otherwise it'll copy the value over from in-effect to text-decorations. wrappingStyle->collapseTextDecorationProperties(); - return wrappingStyle.release(); + return wrappingStyle; } - wrappingStyle = EditingStyle::create(); + auto wrappingStyle = EditingStyle::create(); // When not annotating for interchange, we only preserve inline style declarations. for (Node* node = context; node && !node->isDocumentNode(); node = node->parentNode()) { - if (node->isStyledElement() && !isMailBlockquote(node)) { - wrappingStyle->mergeInlineAndImplicitStyleOfElement(toStyledElement(node), EditingStyle::DoNotOverrideValues, + if (is<StyledElement>(*node) && !isMailBlockquote(node)) { + wrappingStyle->mergeInlineAndImplicitStyleOfElement(downcast<StyledElement>(node), EditingStyle::DoNotOverrideValues, EditingStyle::EditingPropertiesInEffect); } } - return wrappingStyle.release(); + return wrappingStyle; } -static void mergeTextDecorationValues(CSSValueList* mergedValue, const CSSValueList* valueToMerge) +static void mergeTextDecorationValues(CSSValueList& mergedValue, const CSSValueList& valueToMerge) { - RefPtr<CSSPrimitiveValue> underline = cssValuePool().createIdentifierValue(CSSValueUnderline); - RefPtr<CSSPrimitiveValue> lineThrough = cssValuePool().createIdentifierValue(CSSValueLineThrough); + auto& cssValuePool = CSSValuePool::singleton(); + Ref<CSSPrimitiveValue> underline = cssValuePool.createIdentifierValue(CSSValueUnderline); + Ref<CSSPrimitiveValue> lineThrough = cssValuePool.createIdentifierValue(CSSValueLineThrough); - if (valueToMerge->hasValue(underline.get()) && !mergedValue->hasValue(underline.get())) - mergedValue->append(underline.get()); + if (valueToMerge.hasValue(underline.ptr()) && !mergedValue.hasValue(underline.ptr())) + mergedValue.append(WTFMove(underline)); - if (valueToMerge->hasValue(lineThrough.get()) && !mergedValue->hasValue(lineThrough.get())) - mergedValue->append(lineThrough.get()); + if (valueToMerge.hasValue(lineThrough.ptr()) && !mergedValue.hasValue(lineThrough.ptr())) + mergedValue.append(WTFMove(lineThrough)); } void EditingStyle::mergeStyle(const StyleProperties* style, CSSPropertyOverrideMode mode) @@ -1093,17 +1241,20 @@ void EditingStyle::mergeStyle(const StyleProperties* style, CSSPropertyOverrideM StyleProperties::PropertyReference property = style->propertyAt(i); RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(property.id()); - // text decorations never override values - if ((property.id() == CSSPropertyTextDecoration || property.id() == CSSPropertyWebkitTextDecorationsInEffect) && property.value()->isValueList() && value) { - if (value->isValueList()) { - mergeTextDecorationValues(toCSSValueList(value.get()), toCSSValueList(property.value())); + // text decorations never override values. + if ((property.id() == CSSPropertyTextDecoration || property.id() == CSSPropertyWebkitTextDecorationsInEffect) + && is<CSSValueList>(*property.value()) && value) { + if (is<CSSValueList>(*value)) { + auto newValue = downcast<CSSValueList>(*value).copy(); + mergeTextDecorationValues(newValue, downcast<CSSValueList>(*property.value())); + m_mutableStyle->setProperty(property.id(), WTFMove(newValue), property.isImportant()); continue; } - value = 0; // text-decoration: none is equivalent to not having the property + value = nullptr; // text-decoration: none is equivalent to not having the property. } if (mode == OverrideValues || (mode == DoNotOverrideValues && !value)) - m_mutableStyle->setProperty(property.id(), property.value()->cssText(), property.isImportant()); + m_mutableStyle->setProperty(property.id(), property.value(), property.isImportant()); } int oldFontSizeDelta = m_fontSizeDelta; @@ -1111,16 +1262,15 @@ void EditingStyle::mergeStyle(const StyleProperties* style, CSSPropertyOverrideM m_fontSizeDelta += oldFontSizeDelta; } -static PassRefPtr<MutableStyleProperties> styleFromMatchedRulesForElement(Element* element, unsigned rulesToInclude) +static Ref<MutableStyleProperties> styleFromMatchedRulesForElement(Element* element, unsigned rulesToInclude) { - RefPtr<MutableStyleProperties> style = MutableStyleProperties::create(); - Vector<RefPtr<StyleRuleBase>> matchedRules = element->document().ensureStyleResolver().styleRulesForElement(element, rulesToInclude); - for (unsigned i = 0; i < matchedRules.size(); ++i) { - if (matchedRules[i]->isStyleRule()) - style->mergeAndOverrideOnConflict(static_pointer_cast<StyleRule>(matchedRules[i])->properties()); + auto style = MutableStyleProperties::create(); + for (auto& matchedRule : element->styleResolver().styleRulesForElement(element, rulesToInclude)) { + if (matchedRule->isStyleRule()) + style->mergeAndOverrideOnConflict(static_pointer_cast<StyleRule>(matchedRule)->properties()); } - return style.release(); + return style; } void EditingStyle::mergeStyleFromRules(StyledElement* element) @@ -1151,18 +1301,18 @@ void EditingStyle::mergeStyleFromRulesForSerialization(StyledElement* element) for (unsigned i = 0; i < propertyCount; ++i) { StyleProperties::PropertyReference property = m_mutableStyle->propertyAt(i); CSSValue* value = property.value(); - if (!value->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(*value)) continue; - if (toCSSPrimitiveValue(value)->isPercentage()) { - if (RefPtr<CSSValue> computedPropertyValue = computedStyle.propertyValue(property.id())) - fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue.release())); + if (downcast<CSSPrimitiveValue>(*value).isPercentage()) { + if (auto computedPropertyValue = computedStyle.propertyValue(property.id())) + fromComputedStyle->addParsedProperty(CSSProperty(property.id(), WTFMove(computedPropertyValue))); } } } m_mutableStyle->mergeAndOverrideOnConflict(*fromComputedStyle); } -static void removePropertiesInStyle(MutableStyleProperties* styleToRemovePropertiesFrom, StyleProperties* style) +static void removePropertiesInStyle(MutableStyleProperties* styleToRemovePropertiesFrom, MutableStyleProperties* style) { unsigned propertyCount = style->propertyCount(); Vector<CSSPropertyID> propertiesToRemove(propertyCount); @@ -1181,7 +1331,7 @@ void EditingStyle::removeStyleFromRulesAndContext(StyledElement* element, Node* // 1. Remove style from matched rules because style remain without repeating it in inline style declaration RefPtr<MutableStyleProperties> styleFromMatchedRules = styleFromMatchedRulesForElement(element, StyleResolver::AllButEmptyCSSRules); if (styleFromMatchedRules && !styleFromMatchedRules->isEmpty()) - m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), styleFromMatchedRules.get()); + m_mutableStyle = getPropertiesNotIn(*m_mutableStyle, *styleFromMatchedRules); // 2. Remove style present in context and not overriden by matched rules. RefPtr<EditingStyle> computedStyle = EditingStyle::create(context, EditingPropertiesInEffect); @@ -1190,15 +1340,15 @@ void EditingStyle::removeStyleFromRulesAndContext(StyledElement* element, Node* computedStyle->m_mutableStyle->setProperty(CSSPropertyBackgroundColor, CSSValueTransparent); removePropertiesInStyle(computedStyle->m_mutableStyle.get(), styleFromMatchedRules.get()); - m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), computedStyle->m_mutableStyle.get()); + m_mutableStyle = getPropertiesNotIn(*m_mutableStyle, *computedStyle->m_mutableStyle); } // 3. If this element is a span and has display: inline or float: none, remove them unless they are overriden by rules. // These rules are added by serialization code to wrap text nodes. if (isStyleSpanOrSpanWithOnlyStyleAttribute(element)) { - if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyDisplay) && identifierForStyleProperty(m_mutableStyle.get(), CSSPropertyDisplay) == CSSValueInline) + if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyDisplay) && identifierForStyleProperty(*m_mutableStyle, CSSPropertyDisplay) == CSSValueInline) m_mutableStyle->removeProperty(CSSPropertyDisplay); - if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyFloat) && identifierForStyleProperty(m_mutableStyle.get(), CSSPropertyFloat) == CSSValueNone) + if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyFloat) && identifierForStyleProperty(*m_mutableStyle, CSSPropertyFloat) == CSSValueNone) m_mutableStyle->removeProperty(CSSPropertyFloat); } } @@ -1208,11 +1358,24 @@ void EditingStyle::removePropertiesInElementDefaultStyle(Element* element) if (!m_mutableStyle || m_mutableStyle->isEmpty()) return; - RefPtr<StyleProperties> defaultStyle = styleFromMatchedRulesForElement(element, StyleResolver::UAAndUserCSSRules); + RefPtr<MutableStyleProperties> defaultStyle = styleFromMatchedRulesForElement(element, StyleResolver::UAAndUserCSSRules); removePropertiesInStyle(m_mutableStyle.get(), defaultStyle.get()); } +template<typename T> +void EditingStyle::removeEquivalentProperties(T& style) +{ + Vector<CSSPropertyID> propertiesToRemove; + for (auto& property : m_mutableStyle->m_propertyVector) { + if (style.propertyMatches(property.id(), property.value())) + propertiesToRemove.append(property.id()); + } + // FIXME: This should use mass removal. + for (auto& property : propertiesToRemove) + m_mutableStyle->removeProperty(property); +} + void EditingStyle::forceInline() { if (!m_mutableStyle) @@ -1221,12 +1384,41 @@ void EditingStyle::forceInline() m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueInline, propertyIsImportant); } +bool EditingStyle::convertPositionStyle() +{ + if (!m_mutableStyle) + return false; + + auto& cssValuePool = CSSValuePool::singleton(); + RefPtr<CSSPrimitiveValue> sticky = cssValuePool.createIdentifierValue(CSSValueWebkitSticky); + if (m_mutableStyle->propertyMatches(CSSPropertyPosition, sticky.get())) { + m_mutableStyle->setProperty(CSSPropertyPosition, cssValuePool.createIdentifierValue(CSSValueStatic), m_mutableStyle->propertyIsImportant(CSSPropertyPosition)); + return false; + } + RefPtr<CSSPrimitiveValue> fixed = cssValuePool.createIdentifierValue(CSSValueFixed); + if (m_mutableStyle->propertyMatches(CSSPropertyPosition, fixed.get())) { + m_mutableStyle->setProperty(CSSPropertyPosition, cssValuePool.createIdentifierValue(CSSValueAbsolute), m_mutableStyle->propertyIsImportant(CSSPropertyPosition)); + return true; + } + RefPtr<CSSPrimitiveValue> absolute = cssValuePool.createIdentifierValue(CSSValueAbsolute); + if (m_mutableStyle->propertyMatches(CSSPropertyPosition, absolute.get())) + return true; + return false; +} + +bool EditingStyle::isFloating() +{ + RefPtr<CSSValue> v = m_mutableStyle->getPropertyCSSValue(CSSPropertyFloat); + RefPtr<CSSPrimitiveValue> noneValue = CSSValuePool::singleton().createIdentifierValue(CSSValueNone); + return v && !v->equals(*noneValue); +} + int EditingStyle::legacyFontSize(Document* document) const { RefPtr<CSSValue> cssValue = m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize); - if (!cssValue || !cssValue->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(cssValue.get())) return 0; - return legacyFontSizeFromCSSValue(document, toCSSPrimitiveValue(cssValue.get()), + return legacyFontSizeFromCSSValue(document, downcast<CSSPrimitiveValue>(cssValue.get()), m_shouldUseFixedDefaultFontSize, AlwaysUseLegacyFontSize); } @@ -1257,7 +1449,7 @@ PassRefPtr<EditingStyle> EditingStyle::styleAtSelectionStart(const VisibleSelect // and find the background color of the common ancestor. if (shouldUseBackgroundColorInEffect && (selection.isRange() || hasTransparentBackgroundColor(style->m_mutableStyle.get()))) { RefPtr<Range> range(selection.toNormalizedRange()); - if (PassRefPtr<CSSValue> value = backgroundColorInEffect(range->commonAncestorContainer(IGNORE_EXCEPTION))) + if (auto value = backgroundColorInEffect(range->commonAncestorContainer())) style->setProperty(CSSPropertyBackgroundColor, value->cssText()); } @@ -1282,15 +1474,15 @@ WritingDirection EditingStyle::textDirectionForSelection(const VisibleSelection& end = selection.end().upstream(); Node* pastLast = Range::create(*end.document(), position.parentAnchoredEquivalent(), end.parentAnchoredEquivalent())->pastLastNode(); - for (Node* n = node; n && n != pastLast; n = NodeTraversal::next(n)) { + for (Node* n = node; n && n != pastLast; n = NodeTraversal::next(*n)) { if (!n->isStyledElement()) continue; RefPtr<CSSValue> unicodeBidi = ComputedStyleExtractor(n).propertyValue(CSSPropertyUnicodeBidi); - if (!unicodeBidi || !unicodeBidi->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(unicodeBidi.get())) continue; - CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID(); + CSSValueID unicodeBidiValue = downcast<CSSPrimitiveValue>(*unicodeBidi).valueID(); if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride) return NaturalWritingDirection; } @@ -1316,10 +1508,10 @@ WritingDirection EditingStyle::textDirectionForSelection(const VisibleSelection& ComputedStyleExtractor computedStyle(node); RefPtr<CSSValue> unicodeBidi = computedStyle.propertyValue(CSSPropertyUnicodeBidi); - if (!unicodeBidi || !unicodeBidi->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(unicodeBidi.get())) continue; - CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID(); + CSSValueID unicodeBidiValue = downcast<CSSPrimitiveValue>(*unicodeBidi).valueID(); if (unicodeBidiValue == CSSValueNormal) continue; @@ -1328,10 +1520,10 @@ WritingDirection EditingStyle::textDirectionForSelection(const VisibleSelection& ASSERT(unicodeBidiValue == CSSValueEmbed); RefPtr<CSSValue> direction = computedStyle.propertyValue(CSSPropertyDirection); - if (!direction || !direction->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(direction.get())) continue; - CSSValueID directionValue = toCSSPrimitiveValue(direction.get())->getValueID(); + CSSValueID directionValue = downcast<CSSPrimitiveValue>(*direction).valueID(); if (directionValue != CSSValueLtr && directionValue != CSSValueRtl) continue; @@ -1339,7 +1531,7 @@ WritingDirection EditingStyle::textDirectionForSelection(const VisibleSelection& return NaturalWritingDirection; // In the range case, make sure that the embedding element persists until the end of the range. - if (selection.isRange() && !end.deprecatedNode()->isDescendantOf(node)) + if (selection.isRange() && !end.deprecatedNode()->isDescendantOf(*node)) return NaturalWritingDirection; foundDirection = directionValue == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection; @@ -1373,8 +1565,8 @@ StyleChange::StyleChange(EditingStyle* style, const Position& position) , m_applySubscript(false) , m_applySuperscript(false) { - Document* document = position.anchorNode() ? &position.anchorNode()->document() : 0; - if (!style || !style->style() || !document || !document->frame()) + Document* document = position.deprecatedNode() ? &position.deprecatedNode()->document() : 0; + if (!style || style->isEmpty() || !document || !document->frame()) return; Node* node = position.containerNode(); @@ -1384,11 +1576,44 @@ StyleChange::StyleChange(EditingStyle* style, const Position& position) ComputedStyleExtractor computedStyle(node); // FIXME: take care of background-color in effect - RefPtr<MutableStyleProperties> mutableStyle = getPropertiesNotIn(style->style(), &computedStyle); + RefPtr<MutableStyleProperties> mutableStyle = style->style() ? + getPropertiesNotIn(*style->style(), computedStyle) : MutableStyleProperties::create(); reconcileTextDecorationProperties(mutableStyle.get()); - if (!document->frame()->editor().shouldStyleWithCSS()) - extractTextStyles(document, mutableStyle.get(), computedStyle.useFixedFontDefaultSize()); + bool shouldStyleWithCSS = document->frame()->editor().shouldStyleWithCSS(); + if (!shouldStyleWithCSS) + extractTextStyles(document, *mutableStyle, computedStyle.useFixedFontDefaultSize()); + + bool shouldAddUnderline = style->underlineChange() == TextDecorationChange::Add; + bool shouldAddStrikeThrough = style->strikeThroughChange() == TextDecorationChange::Add; + if (shouldAddUnderline || shouldAddStrikeThrough) { + RefPtr<CSSValue> value = computedStyle.propertyValue(CSSPropertyWebkitTextDecorationsInEffect); + if (!is<CSSValueList>(value.get())) + value = computedStyle.propertyValue(CSSPropertyTextDecoration); + + RefPtr<CSSValueList> valueList; + if (is<CSSValueList>(value.get())) + valueList = downcast<CSSValueList>(value.get()); + + auto& cssValuePool = CSSValuePool::singleton(); + Ref<CSSValue> underline = cssValuePool.createIdentifierValue(CSSValueUnderline); + bool hasUnderline = valueList && valueList->hasValue(underline.ptr()); + + Ref<CSSValue> lineThrough = cssValuePool.createIdentifierValue(CSSValueLineThrough); + bool hasLineThrough = valueList && valueList->hasValue(lineThrough.ptr()); + + if (shouldStyleWithCSS) { + valueList = valueList ? valueList->copy() : CSSValueList::createSpaceSeparated(); + if (shouldAddUnderline && !hasUnderline) + valueList->append(WTFMove(underline)); + if (shouldAddStrikeThrough && !hasLineThrough) + valueList->append(WTFMove(lineThrough)); + mutableStyle->setProperty(CSSPropertyTextDecoration, valueList.get()); + } else { + m_applyUnderline = shouldAddUnderline && !hasUnderline; + m_applyLineThrough = shouldAddStrikeThrough && !hasLineThrough; + } + } // Changing the whitespace style in a tab span would collapse the tab into a space. if (isTabSpanTextNode(position.deprecatedNode()) || isTabSpanNode((position.deprecatedNode()))) @@ -1399,43 +1624,59 @@ StyleChange::StyleChange(EditingStyle* style, const Position& position) if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection)) mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection)); - // Save the result for later - m_cssStyle = mutableStyle->asText().stripWhiteSpace(); + if (!mutableStyle->isEmpty()) + m_cssStyle = mutableStyle; +} + +bool StyleChange::operator==(const StyleChange& other) +{ + if (m_applyBold != other.m_applyBold + || m_applyItalic != other.m_applyItalic + || m_applyUnderline != other.m_applyUnderline + || m_applyLineThrough != other.m_applyLineThrough + || m_applySubscript != other.m_applySubscript + || m_applySuperscript != other.m_applySuperscript + || m_applyFontColor != other.m_applyFontColor + || m_applyFontFace != other.m_applyFontFace + || m_applyFontSize != other.m_applyFontSize) + return false; + + return (!m_cssStyle && !other.m_cssStyle) + || (m_cssStyle && other.m_cssStyle && m_cssStyle->asText() == other.m_cssStyle->asText()); } -static void setTextDecorationProperty(MutableStyleProperties* style, const CSSValueList* newTextDecoration, CSSPropertyID propertyID) +static void setTextDecorationProperty(MutableStyleProperties& style, const CSSValueList* newTextDecoration, CSSPropertyID propertyID) { if (newTextDecoration->length()) - style->setProperty(propertyID, newTextDecoration->cssText(), style->propertyIsImportant(propertyID)); + style.setProperty(propertyID, newTextDecoration->cssText(), style.propertyIsImportant(propertyID)); else { // text-decoration: none is redundant since it does not remove any text decorations. - style->removeProperty(propertyID); + style.removeProperty(propertyID); } } -void StyleChange::extractTextStyles(Document* document, MutableStyleProperties* style, bool shouldUseFixedFontDefaultSize) +void StyleChange::extractTextStyles(Document* document, MutableStyleProperties& style, bool shouldUseFixedFontDefaultSize) { - ASSERT(style); - if (identifierForStyleProperty(style, CSSPropertyFontWeight) == CSSValueBold) { - style->removeProperty(CSSPropertyFontWeight); + style.removeProperty(CSSPropertyFontWeight); m_applyBold = true; } int fontStyle = identifierForStyleProperty(style, CSSPropertyFontStyle); if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) { - style->removeProperty(CSSPropertyFontStyle); + style.removeProperty(CSSPropertyFontStyle); m_applyItalic = true; } // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList. - RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration); - if (textDecoration && textDecoration->isValueList()) { - RefPtr<CSSPrimitiveValue> underline = cssValuePool().createIdentifierValue(CSSValueUnderline); - RefPtr<CSSPrimitiveValue> lineThrough = cssValuePool().createIdentifierValue(CSSValueLineThrough); + RefPtr<CSSValue> textDecoration = style.getPropertyCSSValue(CSSPropertyTextDecoration); + if (is<CSSValueList>(textDecoration.get())) { + auto& cssValuePool = CSSValuePool::singleton(); + RefPtr<CSSPrimitiveValue> underline = cssValuePool.createIdentifierValue(CSSValueUnderline); + RefPtr<CSSPrimitiveValue> lineThrough = cssValuePool.createIdentifierValue(CSSValueLineThrough); - RefPtr<CSSValueList> newTextDecoration = toCSSValueList(textDecoration.get())->copy(); + RefPtr<CSSValueList> newTextDecoration = downcast<CSSValueList>(*textDecoration).copy(); if (newTextDecoration->removeAll(underline.get())) m_applyUnderline = true; if (newTextDecoration->removeAll(lineThrough.get())) @@ -1448,61 +1689,58 @@ void StyleChange::extractTextStyles(Document* document, MutableStyleProperties* int verticalAlign = identifierForStyleProperty(style, CSSPropertyVerticalAlign); switch (verticalAlign) { case CSSValueSub: - style->removeProperty(CSSPropertyVerticalAlign); + style.removeProperty(CSSPropertyVerticalAlign); m_applySubscript = true; break; case CSSValueSuper: - style->removeProperty(CSSPropertyVerticalAlign); + style.removeProperty(CSSPropertyVerticalAlign); m_applySuperscript = true; break; } - if (style->getPropertyCSSValue(CSSPropertyColor)) { + if (style.getPropertyCSSValue(CSSPropertyColor)) { m_applyFontColor = Color(textColorFromStyle(style)).serialized(); - style->removeProperty(CSSPropertyColor); + style.removeProperty(CSSPropertyColor); } - m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily); - // Remove single quotes for Outlook 2007 compatibility. See https://bugs.webkit.org/show_bug.cgi?id=79448 - m_applyFontFace.replaceWithLiteral('\'', ""); - style->removeProperty(CSSPropertyFontFamily); + m_applyFontFace = style.getPropertyValue(CSSPropertyFontFamily); + // Remove quotes for Outlook 2007 compatibility. See https://bugs.webkit.org/show_bug.cgi?id=79448 + m_applyFontFace.replaceWithLiteral('\"', ""); + style.removeProperty(CSSPropertyFontFamily); - if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) { - if (!fontSize->isPrimitiveValue()) - style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size. - else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, toCSSPrimitiveValue(fontSize.get()), + if (RefPtr<CSSValue> fontSize = style.getPropertyCSSValue(CSSPropertyFontSize)) { + if (!is<CSSPrimitiveValue>(*fontSize)) + style.removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size. + else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, downcast<CSSPrimitiveValue>(fontSize.get()), shouldUseFixedFontDefaultSize, UseLegacyFontSizeOnlyIfPixelValuesMatch)) { m_applyFontSize = String::number(legacyFontSize); - style->removeProperty(CSSPropertyFontSize); + style.removeProperty(CSSPropertyFontSize); } } } -static void diffTextDecorations(MutableStyleProperties* style, CSSPropertyID propertID, CSSValue* refTextDecoration) +static void diffTextDecorations(MutableStyleProperties& style, CSSPropertyID propertID, CSSValue* refTextDecoration) { - RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID); - if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList()) + RefPtr<CSSValue> textDecoration = style.getPropertyCSSValue(propertID); + if (!is<CSSValueList>(textDecoration.get()) || !is<CSSValueList>(refTextDecoration)) return; - RefPtr<CSSValueList> newTextDecoration = toCSSValueList(textDecoration.get())->copy(); - CSSValueList* valuesInRefTextDecoration = toCSSValueList(refTextDecoration); + RefPtr<CSSValueList> newTextDecoration = downcast<CSSValueList>(*textDecoration).copy(); - for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++) - newTextDecoration->removeAll(valuesInRefTextDecoration->item(i)); + for (auto& value : downcast<CSSValueList>(*refTextDecoration)) + newTextDecoration->removeAll(&value.get()); setTextDecorationProperty(style, newTextDecoration.get(), propertID); } -static bool fontWeightIsBold(CSSValue* fontWeight) +static bool fontWeightIsBold(CSSValue& fontWeight) { - if (!fontWeight) - return false; - if (!fontWeight->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(fontWeight)) return false; // Because b tag can only bold text, there are only two states in plain html: bold and not bold. // Collapse all other values to either one of these two states for editing purposes. - switch (toCSSPrimitiveValue(fontWeight)->getValueID()) { + switch (downcast<CSSPrimitiveValue>(fontWeight).valueID()) { case CSSValue100: case CSSValue200: case CSSValue300: @@ -1525,41 +1763,42 @@ static bool fontWeightIsBold(CSSValue* fontWeight) } template<typename T> -static bool fontWeightIsBold(T* style) +static bool fontWeightIsBold(T& style) { - return fontWeightIsBold(extractPropertyValue(style, CSSPropertyFontWeight).get()); + RefPtr<CSSValue> fontWeight = extractPropertyValue(style, CSSPropertyFontWeight); + return fontWeight && fontWeightIsBold(*fontWeight); } template<typename T> -static PassRefPtr<MutableStyleProperties> extractPropertiesNotIn(StyleProperties* styleWithRedundantProperties, T* baseStyle) +static Ref<MutableStyleProperties> extractPropertiesNotIn(StyleProperties& styleWithRedundantProperties, T& baseStyle) { - ASSERT(styleWithRedundantProperties); - RefPtr<MutableStyleProperties> result = styleWithRedundantProperties->mutableCopy(); - + RefPtr<EditingStyle> result = EditingStyle::create(&styleWithRedundantProperties); result->removeEquivalentProperties(baseStyle); + ASSERT(result->style()); + Ref<MutableStyleProperties> mutableStyle = *result->style(); RefPtr<CSSValue> baseTextDecorationsInEffect = extractPropertyValue(baseStyle, CSSPropertyWebkitTextDecorationsInEffect); - diffTextDecorations(result.get(), CSSPropertyTextDecoration, baseTextDecorationsInEffect.get()); - diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, baseTextDecorationsInEffect.get()); + diffTextDecorations(mutableStyle, CSSPropertyTextDecoration, baseTextDecorationsInEffect.get()); + diffTextDecorations(mutableStyle, CSSPropertyWebkitTextDecorationsInEffect, baseTextDecorationsInEffect.get()); - if (extractPropertyValue(baseStyle, CSSPropertyFontWeight) && fontWeightIsBold(result.get()) == fontWeightIsBold(baseStyle)) - result->removeProperty(CSSPropertyFontWeight); + if (extractPropertyValue(baseStyle, CSSPropertyFontWeight) && fontWeightIsBold(mutableStyle) == fontWeightIsBold(baseStyle)) + mutableStyle->removeProperty(CSSPropertyFontWeight); - if (extractPropertyValue(baseStyle, CSSPropertyColor) && textColorFromStyle(result.get()) == textColorFromStyle(baseStyle)) - result->removeProperty(CSSPropertyColor); + if (extractPropertyValue(baseStyle, CSSPropertyColor) && textColorFromStyle(mutableStyle) == textColorFromStyle(baseStyle)) + mutableStyle->removeProperty(CSSPropertyColor); if (extractPropertyValue(baseStyle, CSSPropertyTextAlign) - && textAlignResolvingStartAndEnd(result.get()) == textAlignResolvingStartAndEnd(baseStyle)) - result->removeProperty(CSSPropertyTextAlign); + && textAlignResolvingStartAndEnd(mutableStyle) == textAlignResolvingStartAndEnd(baseStyle)) + mutableStyle->removeProperty(CSSPropertyTextAlign); - if (extractPropertyValue(baseStyle, CSSPropertyBackgroundColor) && backgroundColorFromStyle(result.get()) == backgroundColorFromStyle(baseStyle)) - result->removeProperty(CSSPropertyBackgroundColor); + if (extractPropertyValue(baseStyle, CSSPropertyBackgroundColor) && backgroundColorFromStyle(mutableStyle) == backgroundColorFromStyle(baseStyle)) + mutableStyle->removeProperty(CSSPropertyBackgroundColor); - return result.release(); + return mutableStyle; } template<typename T> -PassRefPtr<MutableStyleProperties> getPropertiesNotIn(StyleProperties* styleWithRedundantProperties, T* baseStyle) +PassRefPtr<MutableStyleProperties> getPropertiesNotIn(StyleProperties& styleWithRedundantProperties, T& baseStyle) { return extractPropertiesNotIn(styleWithRedundantProperties, baseStyle); } @@ -1574,7 +1813,7 @@ int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, boo ASSERT(document); // FIXME: This method should take a Document& if (isCSSValueLength(value)) { - int pixelFontSize = value->getIntValue(CSSPrimitiveValue::CSS_PX); + int pixelFontSize = value->intValue(CSSPrimitiveValue::CSS_PX); int legacyFontSize = Style::legacyFontSizeForPixelSize(pixelFontSize, shouldUseFixedFontDefaultSize, *document); // Use legacy font size only if pixel value matches exactly to that of legacy font size. int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall; @@ -1584,39 +1823,38 @@ int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, boo return 0; } - if (CSSValueXSmall <= value->getValueID() && value->getValueID() <= CSSValueWebkitXxxLarge) - return value->getValueID() - CSSValueXSmall + 1; + if (CSSValueXSmall <= value->valueID() && value->valueID() <= CSSValueWebkitXxxLarge) + return value->valueID() - CSSValueXSmall + 1; return 0; } -bool isTransparentColorValue(CSSValue* cssValue) +static bool isTransparentColorValue(CSSValue* value) { - if (!cssValue) + if (!value) return true; - if (!cssValue->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(*value)) return false; - CSSPrimitiveValue* value = toCSSPrimitiveValue(cssValue); - if (value->isRGBColor()) - return !alphaChannel(value->getRGBA32Value()); - return value->getValueID() == CSSValueTransparent; + auto& primitiveValue = downcast<CSSPrimitiveValue>(*value); + if (primitiveValue.isRGBColor()) + return !primitiveValue.color().isVisible(); + return primitiveValue.valueID() == CSSValueTransparent; } bool hasTransparentBackgroundColor(StyleProperties* style) { - RefPtr<CSSValue> cssValue = style->getPropertyCSSValue(CSSPropertyBackgroundColor); - return isTransparentColorValue(cssValue.get()); + return isTransparentColorValue(style->getPropertyCSSValue(CSSPropertyBackgroundColor).get()); } -PassRefPtr<CSSValue> backgroundColorInEffect(Node* node) +RefPtr<CSSValue> backgroundColorInEffect(Node* node) { for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) { - if (RefPtr<CSSValue> value = ComputedStyleExtractor(ancestor).propertyValue(CSSPropertyBackgroundColor)) { + if (auto value = ComputedStyleExtractor(ancestor).propertyValue(CSSPropertyBackgroundColor)) { if (!isTransparentColorValue(value.get())) - return value.release(); + return value; } } - return 0; + return nullptr; } } diff --git a/Source/WebCore/editing/EditingStyle.h b/Source/WebCore/editing/EditingStyle.h index 1fa1cde6a..b9e52e333 100644 --- a/Source/WebCore/editing/EditingStyle.h +++ b/Source/WebCore/editing/EditingStyle.h @@ -29,11 +29,11 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef EditingStyle_h -#define EditingStyle_h +#pragma once #include "CSSPropertyNames.h" #include "CSSValueKeywords.h" +#include "StyleProperties.h" #include "WritingDirection.h" #include <wtf/Forward.h> #include <wtf/RefCounted.h> @@ -48,6 +48,7 @@ class CSSStyleDeclaration; class CSSComputedStyleDeclaration; class CSSPrimitiveValue; class CSSValue; +class ComputedStyleExtractor; class Document; class Element; class HTMLElement; @@ -60,6 +61,8 @@ class StyleProperties; class StyledElement; class VisibleSelection; +enum class TextDecorationChange { None, Add, Remove }; + class EditingStyle : public RefCounted<EditingStyle> { public: @@ -69,38 +72,50 @@ public: enum ShouldExtractMatchingStyle { ExtractMatchingStyle, DoNotExtractMatchingStyle }; static float NoFontDelta; - static PassRefPtr<EditingStyle> create() + static Ref<EditingStyle> create() + { + return adoptRef(*new EditingStyle); + } + + static Ref<EditingStyle> create(Node* node, PropertiesToInclude propertiesToInclude = OnlyEditingInheritableProperties) + { + return adoptRef(*new EditingStyle(node, propertiesToInclude)); + } + + static Ref<EditingStyle> create(const Position& position, PropertiesToInclude propertiesToInclude = OnlyEditingInheritableProperties) { - return adoptRef(new EditingStyle()); + return adoptRef(*new EditingStyle(position, propertiesToInclude)); } - static PassRefPtr<EditingStyle> create(Node* node, PropertiesToInclude propertiesToInclude = OnlyEditingInheritableProperties) + static Ref<EditingStyle> create(const StyleProperties* style) { - return adoptRef(new EditingStyle(node, propertiesToInclude)); + return adoptRef(*new EditingStyle(style)); } - static PassRefPtr<EditingStyle> create(const Position& position, PropertiesToInclude propertiesToInclude = OnlyEditingInheritableProperties) + static Ref<EditingStyle> create(const CSSStyleDeclaration* style) { - return adoptRef(new EditingStyle(position, propertiesToInclude)); + return adoptRef(*new EditingStyle(style)); } - static PassRefPtr<EditingStyle> create(const StyleProperties* style) + static Ref<EditingStyle> create(CSSPropertyID propertyID, const String& value) { - return adoptRef(new EditingStyle(style)); + return adoptRef(*new EditingStyle(propertyID, value)); } - static PassRefPtr<EditingStyle> create(CSSPropertyID propertyID, const String& value) + static Ref<EditingStyle> create(CSSPropertyID propertyID, CSSValueID value) { - return adoptRef(new EditingStyle(propertyID, value)); + return adoptRef(*new EditingStyle(propertyID, value)); } - ~EditingStyle(); + WEBCORE_EXPORT ~EditingStyle(); MutableStyleProperties* style() { return m_mutableStyle.get(); } + Ref<MutableStyleProperties> styleWithResolvedTextDecorations() const; bool textDirection(WritingDirection&) const; bool isEmpty() const; void setStyle(PassRefPtr<MutableStyleProperties>); void overrideWithStyle(const StyleProperties*); + void overrideTypingStyleAt(const EditingStyle&, const Position&); void clear(); PassRefPtr<EditingStyle> copy() const; PassRefPtr<EditingStyle> extractAndRemoveBlockProperties(); @@ -108,16 +123,18 @@ public: void removeBlockProperties(); void removeStyleAddedByNode(Node*); void removeStyleConflictingWithStyleOfNode(Node*); + template<typename T> void removeEquivalentProperties(T&); void collapseTextDecorationProperties(); enum ShouldIgnoreTextOnlyProperties { IgnoreTextOnlyProperties, DoNotIgnoreTextOnlyProperties }; TriState triStateOfStyle(EditingStyle*) const; TriState triStateOfStyle(const VisibleSelection&) const; bool conflictsWithInlineStyleOfElement(StyledElement* element) const { return conflictsWithInlineStyleOfElement(element, 0, 0); } - bool conflictsWithInlineStyleOfElement(StyledElement* element, EditingStyle* extractedStyle, Vector<CSSPropertyID>& conflictingProperties) const + bool conflictsWithInlineStyleOfElement(StyledElement* element, RefPtr<MutableStyleProperties>& newInlineStyle, + EditingStyle* extractedStyle) const { - return conflictsWithInlineStyleOfElement(element, extractedStyle, &conflictingProperties); + return conflictsWithInlineStyleOfElement(element, &newInlineStyle, extractedStyle); } - bool conflictsWithImplicitStyleOfElement(HTMLElement*, EditingStyle* extractedStyle = 0, ShouldExtractMatchingStyle = DoNotExtractMatchingStyle) const; + bool conflictsWithImplicitStyleOfElement(HTMLElement*, EditingStyle* extractedStyle = nullptr, ShouldExtractMatchingStyle = DoNotExtractMatchingStyle) const; bool conflictsWithImplicitStyleOfAttributes(HTMLElement*) const; bool extractConflictingImplicitStyleOfAttributes(HTMLElement*, ShouldPreserveWritingDirection, EditingStyle* extractedStyle, Vector<QualifiedName>& conflictingAttributes, ShouldExtractMatchingStyle) const; @@ -129,58 +146,62 @@ public: void mergeTypingStyle(Document&); enum CSSPropertyOverrideMode { OverrideValues, DoNotOverrideValues }; void mergeInlineStyleOfElement(StyledElement*, CSSPropertyOverrideMode, PropertiesToInclude = AllProperties); - static PassRefPtr<EditingStyle> wrappingStyleForSerialization(Node* context, bool shouldAnnotate); + static Ref<EditingStyle> wrappingStyleForSerialization(Node* context, bool shouldAnnotate); void mergeStyleFromRules(StyledElement*); void mergeStyleFromRulesForSerialization(StyledElement*); void removeStyleFromRulesAndContext(StyledElement*, Node* context); void removePropertiesInElementDefaultStyle(Element*); void forceInline(); + bool convertPositionStyle(); + bool isFloating(); int legacyFontSize(Document*) const; float fontSizeDelta() const { return m_fontSizeDelta; } bool hasFontSizeDelta() const { return m_fontSizeDelta != NoFontDelta; } bool shouldUseFixedDefaultFontSize() const { return m_shouldUseFixedDefaultFontSize; } + + void setUnderlineChange(TextDecorationChange change) { m_underlineChange = static_cast<unsigned>(change); } + TextDecorationChange underlineChange() const { return static_cast<TextDecorationChange>(m_underlineChange); } + void setStrikeThroughChange(TextDecorationChange change) { m_strikeThroughChange = static_cast<unsigned>(change); } + TextDecorationChange strikeThroughChange() const { return static_cast<TextDecorationChange>(m_strikeThroughChange); } - static PassRefPtr<EditingStyle> styleAtSelectionStart(const VisibleSelection&, bool shouldUseBackgroundColorInEffect = false); + WEBCORE_EXPORT static PassRefPtr<EditingStyle> styleAtSelectionStart(const VisibleSelection&, bool shouldUseBackgroundColorInEffect = false); static WritingDirection textDirectionForSelection(const VisibleSelection&, EditingStyle* typingStyle, bool& hasNestedOrMultipleEmbeddings); + private: EditingStyle(); EditingStyle(Node*, PropertiesToInclude); EditingStyle(const Position&, PropertiesToInclude); + WEBCORE_EXPORT explicit EditingStyle(const CSSStyleDeclaration*); explicit EditingStyle(const StyleProperties*); EditingStyle(CSSPropertyID, const String& value); + EditingStyle(CSSPropertyID, CSSValueID); void init(Node*, PropertiesToInclude); - void removeTextFillAndStrokeColorsIfNeeded(RenderStyle*); + void removeTextFillAndStrokeColorsIfNeeded(const RenderStyle*); void setProperty(CSSPropertyID, const String& value, bool important = false); void extractFontSizeDelta(); - template<typename T> - TriState triStateOfStyle(T* styleToCompare, ShouldIgnoreTextOnlyProperties) const; - bool conflictsWithInlineStyleOfElement(StyledElement*, EditingStyle* extractedStyle, Vector<CSSPropertyID>* conflictingProperties) const; + template<typename T> TriState triStateOfStyle(T& styleToCompare, ShouldIgnoreTextOnlyProperties) const; + bool conflictsWithInlineStyleOfElement(StyledElement*, RefPtr<MutableStyleProperties>* newInlineStyle, EditingStyle* extractedStyle) const; void mergeInlineAndImplicitStyleOfElement(StyledElement*, CSSPropertyOverrideMode, PropertiesToInclude); void mergeStyle(const StyleProperties*, CSSPropertyOverrideMode); RefPtr<MutableStyleProperties> m_mutableStyle; - bool m_shouldUseFixedDefaultFontSize; - float m_fontSizeDelta; + unsigned m_shouldUseFixedDefaultFontSize : 1; + unsigned m_underlineChange : 2; + unsigned m_strikeThroughChange : 2; + float m_fontSizeDelta = NoFontDelta; friend class HTMLElementEquivalent; friend class HTMLAttributeEquivalent; + friend class HTMLTextDecorationEquivalent; }; class StyleChange { public: - StyleChange() - : m_applyBold(false) - , m_applyItalic(false) - , m_applyUnderline(false) - , m_applyLineThrough(false) - , m_applySubscript(false) - , m_applySuperscript(false) - { } - + StyleChange() { } StyleChange(EditingStyle*, const Position&); - String cssStyle() const { return m_cssStyle; } + const StyleProperties* cssStyle() const { return m_cssStyle.get(); } bool applyBold() const { return m_applyBold; } bool applyItalic() const { return m_applyItalic; } bool applyUnderline() const { return m_applyUnderline; } @@ -195,38 +216,24 @@ public: String fontFace() { return m_applyFontFace; } String fontSize() { return m_applyFontSize; } - bool operator==(const StyleChange& other) - { - return m_cssStyle == other.m_cssStyle - && m_applyBold == other.m_applyBold - && m_applyItalic == other.m_applyItalic - && m_applyUnderline == other.m_applyUnderline - && m_applyLineThrough == other.m_applyLineThrough - && m_applySubscript == other.m_applySubscript - && m_applySuperscript == other.m_applySuperscript - && m_applyFontColor == other.m_applyFontColor - && m_applyFontFace == other.m_applyFontFace - && m_applyFontSize == other.m_applyFontSize; - } + bool operator==(const StyleChange&); bool operator!=(const StyleChange& other) { return !(*this == other); } private: - void extractTextStyles(Document*, MutableStyleProperties*, bool shouldUseFixedFontDefaultSize); - - String m_cssStyle; - bool m_applyBold; - bool m_applyItalic; - bool m_applyUnderline; - bool m_applyLineThrough; - bool m_applySubscript; - bool m_applySuperscript; + void extractTextStyles(Document*, MutableStyleProperties&, bool shouldUseFixedFontDefaultSize); + + RefPtr<MutableStyleProperties> m_cssStyle; + bool m_applyBold = false; + bool m_applyItalic = false; + bool m_applyUnderline = false; + bool m_applyLineThrough = false; + bool m_applySubscript = false; + bool m_applySuperscript = false; String m_applyFontColor; String m_applyFontFace; String m_applyFontSize; }; } // namespace WebCore - -#endif // EditingStyle_h diff --git a/Source/WebCore/editing/Editor.cpp b/Source/WebCore/editing/Editor.cpp index e599dbd8b..458c33bf0 100644 --- a/Source/WebCore/editing/Editor.cpp +++ b/Source/WebCore/editing/Editor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006, 2007, 2008, 2011, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2006-2016 Apple Inc. All rights reserved. * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) * * Redistribution and use in source and binary forms, with or without @@ -11,10 +11,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -33,10 +33,10 @@ #include "CSSComputedStyleDeclaration.h" #include "CSSPropertyNames.h" #include "CachedResourceLoader.h" -#include "Clipboard.h" #include "ClipboardEvent.h" #include "CompositionEvent.h" #include "CreateLinkCommand.h" +#include "DataTransfer.h" #include "DeleteSelectionCommand.h" #include "DictationAlternative.h" #include "DictationCommand.h" @@ -45,31 +45,39 @@ #include "EditorClient.h" #include "EventHandler.h" #include "EventNames.h" -#include "ExceptionCodePlaceholder.h" #include "FocusController.h" #include "Frame.h" #include "FrameTree.h" #include "FrameView.h" #include "GraphicsContext.h" +#include "HTMLCollection.h" #include "HTMLFormControlElement.h" #include "HTMLFrameOwnerElement.h" #include "HTMLImageElement.h" +#include "HTMLInputElement.h" #include "HTMLNames.h" -#include "HTMLTextAreaElement.h" +#include "HTMLSpanElement.h" #include "HitTestResult.h" #include "IndentOutdentCommand.h" +#include "InputEvent.h" #include "InsertListCommand.h" +#include "InsertTextCommand.h" #include "KeyboardEvent.h" #include "KillRing.h" +#include "Logging.h" +#include "MainFrame.h" #include "ModifySelectionListLevel.h" #include "NodeList.h" #include "NodeTraversal.h" #include "Page.h" #include "Pasteboard.h" +#include "Range.h" #include "RemoveFormatCommand.h" #include "RenderBlock.h" #include "RenderTextControl.h" +#include "RenderedDocumentMarker.h" #include "RenderedPosition.h" +#include "ReplaceRangeWithTextCommand.h" #include "ReplaceSelectionCommand.h" #include "Settings.h" #include "ShadowRoot.h" @@ -78,6 +86,7 @@ #include "SpellChecker.h" #include "SpellingCorrectionCommand.h" #include "StyleProperties.h" +#include "TelephoneNumberDetector.h" #include "Text.h" #include "TextCheckerClient.h" #include "TextCheckingHelper.h" @@ -89,28 +98,60 @@ #include "htmlediting.h" #include "markup.h" #include <wtf/unicode/CharacterNames.h> -#include <wtf/unicode/Unicode.h> -#if ENABLE(DELETION_UI) -#include "DeleteButtonController.h" -#endif - -#if PLATFORM(IOS) -#include "DictationCommandIOS.h" -#include <wtf/text/StringBuilder.h> -#include <wtf/text/WTFString.h> +#if PLATFORM(MAC) +#include "ServicesOverlayController.h" #endif namespace WebCore { -#if PLATFORM(IOS) +static bool dispatchBeforeInputEvent(Element& element, const AtomicString& inputType, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }, bool cancelable = true) +{ + if (!element.document().settings().inputEventsEnabled()) + return true; + + return element.dispatchEvent(InputEvent::create(eventNames().beforeinputEvent, inputType, true, cancelable, element.document().defaultView(), data, WTFMove(dataTransfer), targetRanges, 0)); +} + +static void dispatchInputEvent(Element& element, const AtomicString& inputType, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }) +{ + if (element.document().settings().inputEventsEnabled()) { + // FIXME: We should not be dispatching to the scoped queue here. Normally, input events are dispatched in CompositeEditCommand::apply after the end of the scope, + // but TypingCommands are special in that existing TypingCommands that are applied again fire input events *from within* the scope by calling typingAddedToOpenCommand. + // Instead, TypingCommands should always dispatch events synchronously after the end of the scoped queue in CompositeEditCommand::apply. To work around this for the + // time being, just revert back to calling dispatchScopedEvent. + element.dispatchScopedEvent(InputEvent::create(eventNames().inputEvent, inputType, true, false, element.document().defaultView(), data, WTFMove(dataTransfer), targetRanges, 0)); + } else + element.dispatchInputEvent(); +} + +static String inputEventDataForEditingStyleAndAction(const StyleProperties* style, EditAction action) +{ + if (!style) + return { }; + + switch (action) { + case EditActionSetColor: + return style->getPropertyValue(CSSPropertyColor); + case EditActionSetWritingDirection: + return style->getPropertyValue(CSSPropertyDirection); + default: + return { }; + } +} + +static String inputEventDataForEditingStyleAndAction(EditingStyle& style, EditAction action) +{ + return inputEventDataForEditingStyleAndAction(style.style(), action); +} + class ClearTextCommand : public DeleteSelectionCommand { public: ClearTextCommand(Document& document); static void CreateAndApply(const RefPtr<Frame> frame); private: - virtual EditAction editingAction() const; + EditAction editingAction() const override; }; ClearTextCommand::ClearTextCommand(Document& document) @@ -133,73 +174,15 @@ void ClearTextCommand::CreateAndApply(const RefPtr<Frame> frame) const VisibleSelection oldSelection = frame->selection().selection(); frame->selection().selectAll(); - RefPtr<ClearTextCommand> clearCommand = adoptRef(new ClearTextCommand(*frame->document())); + auto clearCommand = adoptRef(*new ClearTextCommand(*frame->document())); clearCommand->setStartingSelection(oldSelection); - applyCommand(clearCommand.release()); + applyCommand(WTFMove(clearCommand)); } -#endif using namespace HTMLNames; using namespace WTF; using namespace Unicode; -#if ENABLE(DELETION_UI) - -PassRefPtr<Range> Editor::avoidIntersectionWithDeleteButtonController(const Range* range) const -{ - if (!range) - return 0; - - Node* startContainer = range->startContainer(); - int startOffset = range->startOffset(); - Node* endContainer = range->endContainer(); - int endOffset = range->endOffset(); - - if (!startContainer) - return 0; - - ASSERT(endContainer); - - Element* element = m_deleteButtonController->containerElement(); - if (startContainer == element || startContainer->isDescendantOf(element)) { - ASSERT(element->parentNode()); - startContainer = element->parentNode(); - startOffset = element->nodeIndex(); - } - if (endContainer == element || endContainer->isDescendantOf(element)) { - ASSERT(element->parentNode()); - endContainer = element->parentNode(); - endOffset = element->nodeIndex(); - } - - return Range::create(range->ownerDocument(), startContainer, startOffset, endContainer, endOffset); -} - -VisibleSelection Editor::avoidIntersectionWithDeleteButtonController(const VisibleSelection& selection) const -{ - if (selection.isNone()) - return selection; - - Element* element = m_deleteButtonController->containerElement(); - if (!element) - return selection; - VisibleSelection updatedSelection = selection; - - Position updatedBase = selection.base(); - updatePositionForNodeRemoval(updatedBase, element); - if (updatedBase != selection.base()) - updatedSelection.setBase(updatedBase); - - Position updatedExtent = selection.extent(); - updatePositionForNodeRemoval(updatedExtent, element); - if (updatedExtent != selection.extent()) - updatedSelection.setExtent(updatedExtent); - - return updatedSelection; -} - -#endif - // When an event handler has moved the selection outside of a text control // we should use the target control's selection for this editing operation. VisibleSelection Editor::selectionForCommand(Event* event) @@ -210,10 +193,10 @@ VisibleSelection Editor::selectionForCommand(Event* event) // If the target is a text control, and the current selection is outside of its shadow tree, // then use the saved selection for that text control. HTMLTextFormControlElement* textFormControlOfSelectionStart = enclosingTextFormControl(selection.start()); - HTMLTextFormControlElement* textFromControlOfTarget = isHTMLTextFormControlElement(*event->target()->toNode()) ? toHTMLTextFormControlElement(event->target()->toNode()) : nullptr; + HTMLTextFormControlElement* textFromControlOfTarget = is<HTMLTextFormControlElement>(*event->target()->toNode()) ? downcast<HTMLTextFormControlElement>(event->target()->toNode()) : nullptr; if (textFromControlOfTarget && (selection.start().isNull() || textFromControlOfTarget != textFormControlOfSelectionStart)) { if (RefPtr<Range> range = textFromControlOfTarget->selection()) - return VisibleSelection(range.get(), DOWNSTREAM, selection.isDirectional()); + return VisibleSelection(*range, DOWNSTREAM, selection.isDirectional()); } return selection; } @@ -227,11 +210,10 @@ EditingBehavior Editor::behavior() const EditorClient* Editor::client() const { if (Page* page = m_frame.page()) - return page->editorClient(); - return 0; + return &page->editorClient(); + return nullptr; } - TextCheckerClient* Editor::textChecker() const { if (EditorClient* owner = client()) @@ -239,59 +221,57 @@ TextCheckerClient* Editor::textChecker() const return 0; } -void Editor::handleKeyboardEvent(KeyboardEvent* event) +void Editor::handleKeyboardEvent(KeyboardEvent& event) { if (EditorClient* c = client()) - c->handleKeyboardEvent(event); + c->handleKeyboardEvent(&event); } -void Editor::handleInputMethodKeydown(KeyboardEvent* event) +void Editor::handleInputMethodKeydown(KeyboardEvent& event) { if (EditorClient* c = client()) - c->handleInputMethodKeydown(event); + c->handleInputMethodKeydown(&event); } -bool Editor::handleTextEvent(TextEvent* event) +bool Editor::handleTextEvent(TextEvent& event) { + LOG(Editing, "Editor %p handleTextEvent (data %s)", this, event.data().utf8().data()); + // Default event handling for Drag and Drop will be handled by DragController // so we leave the event for it. - if (event->isDrop()) + if (event.isDrop()) return false; - if (event->isPaste()) { - if (event->pastingFragment()) + if (event.isPaste()) { + if (event.pastingFragment()) { #if PLATFORM(IOS) - { - if (client()->performsTwoStepPaste(event->pastingFragment())) + if (client()->performsTwoStepPaste(event.pastingFragment())) return true; #endif - replaceSelectionWithFragment(event->pastingFragment(), false, event->shouldSmartReplace(), event->shouldMatchStyle()); -#if PLATFORM(IOS) - } -#endif - else - replaceSelectionWithText(event->data(), false, event->shouldSmartReplace()); + replaceSelectionWithFragment(event.pastingFragment(), false, event.shouldSmartReplace(), event.shouldMatchStyle(), EditActionPaste, event.mailBlockquoteHandling()); + } else + replaceSelectionWithText(event.data(), false, event.shouldSmartReplace(), EditActionPaste); return true; } - String data = event->data(); + String data = event.data(); if (data == "\n") { - if (event->isLineBreak()) + if (event.isLineBreak()) return insertLineBreak(); return insertParagraphSeparator(); } - return insertTextWithoutSendingTextEvent(data, false, event); + return insertTextWithoutSendingTextEvent(data, false, &event); } bool Editor::canEdit() const { - return m_frame.selection().rootEditableElement(); + return m_frame.selection().selection().rootEditableElement(); } bool Editor::canEditRichly() const { - return m_frame.selection().isContentRichlyEditable(); + return m_frame.selection().selection().isContentRichlyEditable(); } // WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They @@ -301,17 +281,17 @@ bool Editor::canEditRichly() const bool Editor::canDHTMLCut() { - return !m_frame.selection().isInPasswordField() && !dispatchCPPEvent(eventNames().beforecutEvent, ClipboardNumb); + return !m_frame.selection().selection().isInPasswordField() && !dispatchCPPEvent(eventNames().beforecutEvent, DataTransferAccessPolicy::Numb); } bool Editor::canDHTMLCopy() { - return !m_frame.selection().isInPasswordField() && !dispatchCPPEvent(eventNames().beforecopyEvent, ClipboardNumb); + return !m_frame.selection().selection().isInPasswordField() && !dispatchCPPEvent(eventNames().beforecopyEvent, DataTransferAccessPolicy::Numb); } bool Editor::canDHTMLPaste() { - return !dispatchCPPEvent(eventNames().beforepasteEvent, ClipboardNumb); + return !dispatchCPPEvent(eventNames().beforepasteEvent, DataTransferAccessPolicy::Numb); } bool Editor::canCut() const @@ -322,25 +302,23 @@ bool Editor::canCut() const static HTMLImageElement* imageElementFromImageDocument(Document& document) { if (!document.isImageDocument()) - return 0; + return nullptr; - HTMLElement* body = document.body(); + HTMLElement* body = document.bodyOrFrameset(); if (!body) - return 0; + return nullptr; Node* node = body->firstChild(); - if (!node) - return 0; - if (!isHTMLImageElement(node)) - return 0; - return toHTMLImageElement(node); + if (!is<HTMLImageElement>(node)) + return nullptr; + return downcast<HTMLImageElement>(node); } bool Editor::canCopy() const { if (imageElementFromImageDocument(document())) return true; - FrameSelection& selection = m_frame.selection(); + const VisibleSelection& selection = m_frame.selection().selection(); return selection.isRange() && !selection.isInPasswordField(); } @@ -351,25 +329,23 @@ bool Editor::canPaste() const bool Editor::canDelete() const { - FrameSelection& selection = m_frame.selection(); + const VisibleSelection& selection = m_frame.selection().selection(); return selection.isRange() && selection.rootEditableElement(); } bool Editor::canDeleteRange(Range* range) const { - Node* startContainer = range->startContainer(); - Node* endContainer = range->endContainer(); - if (!startContainer || !endContainer) - return false; + Node& startContainer = range->startContainer(); + Node& endContainer = range->endContainer(); - if (!startContainer->hasEditableStyle() || !endContainer->hasEditableStyle()) + if (!startContainer.hasEditableStyle() || !endContainer.hasEditableStyle()) return false; - if (range->collapsed(IGNORE_EXCEPTION)) { + if (range->collapsed()) { VisiblePosition start(range->startPosition(), DOWNSTREAM); VisiblePosition previous = start.previous(); // FIXME: We sometimes allow deletions at the start of editable roots, like when the caret is in an empty list item. - if (previous.isNull() || previous.deepEquivalent().deprecatedNode()->rootEditableElement() != startContainer->rootEditableElement()) + if (previous.isNull() || previous.deepEquivalent().deprecatedNode()->rootEditableElement() != startContainer.rootEditableElement()) return false; } return true; @@ -390,7 +366,7 @@ bool Editor::isSelectTrailingWhitespaceEnabled() return client() && client()->isSelectTrailingWhitespaceEnabled(); } -bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity granularity, bool killRing, bool isTypingAction) +bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity granularity, bool shouldAddToKillRing, bool isTypingAction) { if (!canEdit()) return false; @@ -400,8 +376,8 @@ bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity g TypingCommand::deleteKeyPressed(document(), canSmartCopyOrDelete() ? TypingCommand::SmartDelete : 0, granularity); revealSelectionAfterEditingOperation(); } else { - if (killRing) - addToKillRing(selectedRange().get(), false); + if (shouldAddToKillRing) + addRangeToKillRing(*selectedRange().get(), KillRingInsertionMode::AppendText); deleteSelectionWithSmartDelete(canSmartCopyOrDelete()); // Implicitly calls revealSelectionAfterEditingOperation(). } @@ -409,8 +385,8 @@ bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity g TypingCommand::Options options = 0; if (canSmartCopyOrDelete()) options |= TypingCommand::SmartDelete; - if (killRing) - options |= TypingCommand::KillRing; + if (shouldAddToKillRing) + options |= TypingCommand::AddsToKillRing; switch (direction) { case DirectionForward: case DirectionRight: @@ -427,112 +403,39 @@ bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity g // FIXME: We should to move this down into deleteKeyPressed. // clear the "start new kill ring sequence" setting, because it was set to true // when the selection was updated by deleting the range - if (killRing) + if (shouldAddToKillRing) setStartNewKillRingSequence(false); return true; } -void Editor::deleteSelectionWithSmartDelete(bool smartDelete) +void Editor::deleteSelectionWithSmartDelete(bool smartDelete, EditAction editingAction) { if (m_frame.selection().isNone()) return; - applyCommand(DeleteSelectionCommand::create(document(), smartDelete)); + applyCommand(DeleteSelectionCommand::create(document(), smartDelete, true, false, false, true, editingAction)); } -#if PLATFORM(IOS) void Editor::clearText() { ClearTextCommand::CreateAndApply(&m_frame); } -void Editor::insertDictationPhrases(PassOwnPtr<Vector<Vector<String> > > dictationPhrases, RetainPtr<id> metadata) -{ - if (m_frame.selection().isNone()) - return; - - if (dictationPhrases->isEmpty()) - return; - - applyCommand(DictationCommandIOS::create(document(), dictationPhrases, metadata)); -} - -void Editor::setDictationPhrasesAsChildOfElement(PassOwnPtr<Vector<Vector<String> > > dictationPhrases, RetainPtr<id> metadata, Element* element) -{ - // Clear the composition. - clear(); - - // Clear the Undo stack, since the operations that follow are not Undoable, and will corrupt the stack. Some day - // we could make them Undoable, and let callers clear the Undo stack explicitly if they wish. - clearUndoRedoOperations(); - - m_frame.selection().clear(); - - element->removeChildren(); - - if (dictationPhrases->isEmpty()) { - client()->respondToChangedContents(); - return; - } - - ExceptionCode ec; - RefPtr<Range> context = document().createRange(); - context->selectNodeContents(element, ec); - - StringBuilder dictationPhrasesBuilder; - size_t dictationPhraseCount = dictationPhrases->size(); - for (size_t i = 0; i < dictationPhraseCount; i++) { - const String& firstInterpretation = dictationPhrases->at(i)[0]; - dictationPhrasesBuilder.append(firstInterpretation); - } - String serializedDictationPhrases = dictationPhrasesBuilder.toString(); - - element->appendChild(createFragmentFromText(*context.get(), serializedDictationPhrases), ec); - - // We need a layout in order to add markers below. - document().updateLayout(); - - if (!element->firstChild()->isTextNode()) { - // Shouldn't happen. - ASSERT(element->firstChild()->isTextNode()); - return; - } - - Text* textNode = static_cast<Text*>(element->firstChild()); - int previousDictationPhraseStart = 0; - for (size_t i = 0; i < dictationPhraseCount; i++) { - const Vector<String>& interpretations = dictationPhrases->at(i); - int dictationPhraseLength = interpretations[0].length(); - int dictationPhraseEnd = previousDictationPhraseStart + dictationPhraseLength; - if (interpretations.size() > 1) { - RefPtr<Range> dictationPhraseRange = Range::create(document(), textNode, previousDictationPhraseStart, textNode, dictationPhraseEnd); - document().markers().addDictationPhraseWithAlternativesMarker(dictationPhraseRange.get(), interpretations); - } - previousDictationPhraseStart = dictationPhraseEnd; - } - - RefPtr<Range> resultRange = Range::create(document(), textNode, 0, textNode, textNode->length()); - document().markers().addDictationResultMarker(resultRange.get(), metadata); - - client()->respondToChangedContents(); -} -#endif - void Editor::pasteAsPlainText(const String& pastingText, bool smartReplace) { Node* target = findEventTargetFromSelection(); if (!target) return; - target->dispatchEvent(TextEvent::createForPlainTextPaste(document().domWindow(), pastingText, smartReplace), IGNORE_EXCEPTION); + target->dispatchEvent(TextEvent::createForPlainTextPaste(document().domWindow(), pastingText, smartReplace)); } -void Editor::pasteAsFragment(PassRefPtr<DocumentFragment> pastingFragment, bool smartReplace, bool matchStyle) +void Editor::pasteAsFragment(Ref<DocumentFragment>&& pastingFragment, bool smartReplace, bool matchStyle, MailBlockquoteHandling respectsMailBlockquote) { Node* target = findEventTargetFromSelection(); if (!target) return; - target->dispatchEvent(TextEvent::createForFragmentPaste(document().domWindow(), pastingFragment, smartReplace, matchStyle), IGNORE_EXCEPTION); + target->dispatchEvent(TextEvent::createForFragmentPaste(document().domWindow(), WTFMove(pastingFragment), smartReplace, matchStyle, respectsMailBlockquote)); } void Editor::pasteAsPlainTextBypassingDHTML() @@ -543,7 +446,7 @@ void Editor::pasteAsPlainTextBypassingDHTML() void Editor::pasteAsPlainTextWithPasteboard(Pasteboard& pasteboard) { String text = readPlainTextFromPasteboard(pasteboard); - if (client() && client()->shouldInsertText(text, selectedRange().get(), EditorInsertActionPasted)) + if (client() && client()->shouldInsertText(text, selectedRange().get(), EditorInsertAction::Pasted)) pasteAsPlainText(text, canSmartReplaceWithPasteboard(pasteboard)); } @@ -554,7 +457,7 @@ String Editor::readPlainTextFromPasteboard(Pasteboard& pasteboard) return plainTextFromPasteboard(text); } -#if !(PLATFORM(MAC) && !PLATFORM(IOS)) +#if !PLATFORM(MAC) String Editor::plainTextFromPasteboard(const PasteboardPlainText& text) { @@ -563,20 +466,6 @@ String Editor::plainTextFromPasteboard(const PasteboardPlainText& text) #endif -#if !PLATFORM(MAC) && !PLATFORM(EFL) -void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText) -{ - RefPtr<Range> range = selectedRange(); - if (!range) - return; - - bool chosePlainText; - RefPtr<DocumentFragment> fragment = pasteboard->documentFragment(m_frame, *range, allowPlainText, chosePlainText); - if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted)) - pasteAsFragment(fragment, canSmartReplaceWithPasteboard(*pasteboard), chosePlainText); -} -#endif - bool Editor::canSmartReplaceWithPasteboard(Pasteboard& pasteboard) { return client() && client()->smartInsertDeleteEnabled() && pasteboard.canSmartReplace(); @@ -589,18 +478,23 @@ bool Editor::shouldInsertFragment(PassRefPtr<DocumentFragment> fragment, PassRef if (fragment) { Node* child = fragment->firstChild(); - if (child && fragment->lastChild() == child && child->isCharacterDataNode()) - return client()->shouldInsertText(toCharacterData(child)->data(), replacingDOMRange.get(), givenAction); + if (is<CharacterData>(child) && fragment->lastChild() == child) + return client()->shouldInsertText(downcast<CharacterData>(*child).data(), replacingDOMRange.get(), givenAction); } return client()->shouldInsertNode(fragment.get(), replacingDOMRange.get(), givenAction); } -void Editor::replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment, bool selectReplacement, bool smartReplace, bool matchStyle) +void Editor::replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment, bool selectReplacement, bool smartReplace, bool matchStyle, EditAction editingAction, MailBlockquoteHandling mailBlockquoteHandling) { - if (m_frame.selection().isNone() || !m_frame.selection().isContentEditable() || !fragment) + VisibleSelection selection = m_frame.selection().selection(); + if (selection.isNone() || !selection.isContentEditable() || !fragment) return; + AccessibilityReplacedText replacedText; + if (AXObjectCache::accessibilityEnabled() && editingAction == EditActionPaste) + replacedText = AccessibilityReplacedText(selection); + ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::PreventNesting | ReplaceSelectionCommand::SanitizeFragment; if (selectReplacement) options |= ReplaceSelectionCommand::SelectReplacement; @@ -608,116 +502,51 @@ void Editor::replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment, options |= ReplaceSelectionCommand::SmartReplace; if (matchStyle) options |= ReplaceSelectionCommand::MatchStyle; - applyCommand(ReplaceSelectionCommand::create(document(), fragment, options, EditActionPaste)); + if (mailBlockquoteHandling == MailBlockquoteHandling::IgnoreBlockquote) + options |= ReplaceSelectionCommand::IgnoreMailBlockquote; + + RefPtr<ReplaceSelectionCommand> command = ReplaceSelectionCommand::create(document(), fragment, options, editingAction); + applyCommand(command); revealSelectionAfterEditingOperation(); - if (m_frame.selection().isInPasswordField() || !isContinuousSpellCheckingEnabled()) + selection = m_frame.selection().selection(); + if (selection.isInPasswordField()) return; - Node* nodeToCheck = m_frame.selection().rootEditableElement(); + + if (AXObjectCache::accessibilityEnabled() && editingAction == EditActionPaste) { + String text = AccessibilityObject::stringForVisiblePositionRange(command->visibleSelectionForInsertedText()); + replacedText.postTextStateChangeNotification(document().existingAXObjectCache(), AXTextEditTypePaste, text, m_frame.selection().selection()); + command->composition()->setRangeDeletedByUnapply(replacedText.replacedRange()); + } + + if (!isContinuousSpellCheckingEnabled()) + return; + + Node* nodeToCheck = selection.rootEditableElement(); if (!nodeToCheck) return; RefPtr<Range> rangeToCheck = Range::create(document(), firstPositionInNode(nodeToCheck), lastPositionInNode(nodeToCheck)); - m_spellChecker->requestCheckingFor(SpellCheckRequest::create(resolveTextCheckingTypeMask(TextCheckingTypeSpelling | TextCheckingTypeGrammar), TextCheckingProcessBatch, rangeToCheck, rangeToCheck)); + m_spellChecker->requestCheckingFor(SpellCheckRequest::create(resolveTextCheckingTypeMask(*nodeToCheck, TextCheckingTypeSpelling | TextCheckingTypeGrammar), TextCheckingProcessBatch, rangeToCheck, rangeToCheck)); } -void Editor::replaceSelectionWithText(const String& text, bool selectReplacement, bool smartReplace) +void Editor::replaceSelectionWithText(const String& text, bool selectReplacement, bool smartReplace, EditAction editingAction) { RefPtr<Range> range = selectedRange(); if (!range) return; - replaceSelectionWithFragment(createFragmentFromText(*range, text), selectReplacement, smartReplace, true); + replaceSelectionWithFragment(createFragmentFromText(*range, text), selectReplacement, smartReplace, true, editingAction); } -PassRefPtr<Range> Editor::selectedRange() +RefPtr<Range> Editor::selectedRange() { return m_frame.selection().toNormalizedRange(); } -#if PLATFORM(IOS) -void Editor::confirmMarkedText() -{ - // FIXME: This is a hacky workaround for the keyboard calling this method too late - - // after the selection and focus have already changed. See <rdar://problem/5975559> - Element* focused = document().focusedElement(); - Node* composition = compositionNode(); - - if (composition && focused && focused != composition && !composition->isDescendantOrShadowDescendantOf(focused)) { - cancelComposition(); - document().setFocusedElement(focused); - } else - confirmComposition(); -} - -void Editor::setTextAsChildOfElement(const String& text, Element* elem) -{ - // Clear the composition - clear(); - - // Clear the Undo stack, since the operations that follow are not Undoable, and will corrupt the stack. Some day - // we could make them Undoable, and let callers clear the Undo stack explicitly if they wish. - clearUndoRedoOperations(); - - // If the element is empty already and we're not adding text, we can early return and avoid clearing/setting - // a selection at [0, 0] and the expense involved in creation VisiblePositions. - if (!elem->firstChild() && text.isEmpty()) - return; - - // As a side effect this function sets a caret selection after the inserted content. Much of what - // follows is more expensive if there is a selection, so clear it since it's going to change anyway. - m_frame.selection().clear(); - - // clear out all current children of element - elem->removeChildren(); - - if (text.length()) { - // insert new text - // remove element from tree while doing it - // FIXME: The element we're inserting into is often the body element. It seems strange to be removing it - // (even if it is only temporary). ReplaceSelectionCommand doesn't bother doing this when it inserts - // content, why should we here? - ExceptionCode ec; - RefPtr<Node> parent = elem->parentNode(); - RefPtr<Node> siblingAfter = elem->nextSibling(); - if (parent) - elem->remove(ec); - - RefPtr<Range> context = document().createRange(); - context->selectNodeContents(elem, ec); - RefPtr<DocumentFragment> fragment = createFragmentFromText(*context.get(), text); - elem->appendChild(fragment, ec); - - // restore element to document - if (parent) { - if (siblingAfter) - parent->insertBefore(elem, siblingAfter.get(), ec); - else - parent->appendChild(elem, ec); - } - } - - // set the selection to the end - VisibleSelection selection; - - Position pos = createLegacyEditingPosition(elem, elem->childNodeCount()); - - VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY); - if (visiblePos.isNull()) - return; - - selection.setBase(visiblePos); - selection.setExtent(visiblePos); - - m_frame.selection().setSelection(selection); - - client()->respondToChangedContents(); -} -#endif - bool Editor::shouldDeleteRange(Range* range) const { - if (!range || range->collapsed(IGNORE_EXCEPTION)) + if (!range || range->collapsed()) return false; if (!canDeleteRange(range)) @@ -728,45 +557,31 @@ bool Editor::shouldDeleteRange(Range* range) const bool Editor::tryDHTMLCopy() { - if (m_frame.selection().isInPasswordField()) + if (m_frame.selection().selection().isInPasswordField()) return false; - return !dispatchCPPEvent(eventNames().copyEvent, ClipboardWritable); + return !dispatchCPPEvent(eventNames().copyEvent, DataTransferAccessPolicy::Writable); } bool Editor::tryDHTMLCut() { - if (m_frame.selection().isInPasswordField()) + if (m_frame.selection().selection().isInPasswordField()) return false; - return !dispatchCPPEvent(eventNames().cutEvent, ClipboardWritable); + return !dispatchCPPEvent(eventNames().cutEvent, DataTransferAccessPolicy::Writable); } bool Editor::tryDHTMLPaste() { - return !dispatchCPPEvent(eventNames().pasteEvent, ClipboardReadable); + return !dispatchCPPEvent(eventNames().pasteEvent, DataTransferAccessPolicy::Readable); } bool Editor::shouldInsertText(const String& text, Range* range, EditorInsertAction action) const { - return client() && client()->shouldInsertText(text, range, action); -} - -void Editor::notifyComponentsOnChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options) -{ -#if PLATFORM(IOS) - // FIXME: Merge this to open source https://bugs.webkit.org/show_bug.cgi?id=38830 - if (m_ignoreCompositionSelectionChange) - return; -#endif + if (m_frame.mainFrame().loader().shouldSuppressKeyboardInput() && action == EditorInsertAction::Typed) + return false; - if (client()) - client()->respondToChangedSelection(&m_frame); - setStartNewKillRingSequence(true); -#if ENABLE(DELETION_UI) - m_deleteButtonController->respondToChangedSelection(oldSelection); -#endif - m_alternativeTextController->respondToChangedSelection(oldSelection, options); + return client() && client()->shouldInsertText(text, range, action); } void Editor::respondToChangedContents(const VisibleSelection& endingSelection) @@ -797,8 +612,11 @@ bool Editor::hasBidiSelection() const } else startNode = m_frame.selection().selection().visibleStart().deepEquivalent().deprecatedNode(); + if (!startNode) + return false; + auto renderer = startNode->renderer(); - while (renderer && !renderer->isRenderBlockFlow()) + while (renderer && !is<RenderBlockFlow>(*renderer)) renderer = renderer->parent(); if (!renderer) @@ -807,17 +625,17 @@ bool Editor::hasBidiSelection() const if (!renderer->style().isLeftToRightDirection()) return true; - return toRenderBlockFlow(renderer)->containsNonZeroBidiLevel(); + return downcast<RenderBlockFlow>(*renderer).containsNonZeroBidiLevel(); } TriState Editor::selectionUnorderedListState() const { if (m_frame.selection().isCaret()) { - if (enclosingNodeWithTag(m_frame.selection().selection().start(), ulTag)) + if (enclosingElementWithTag(m_frame.selection().selection().start(), ulTag)) return TrueTriState; } else if (m_frame.selection().isRange()) { - Node* startNode = enclosingNodeWithTag(m_frame.selection().selection().start(), ulTag); - Node* endNode = enclosingNodeWithTag(m_frame.selection().selection().end(), ulTag); + auto* startNode = enclosingElementWithTag(m_frame.selection().selection().start(), ulTag); + auto* endNode = enclosingElementWithTag(m_frame.selection().selection().end(), ulTag); if (startNode && endNode && startNode == endNode) return TrueTriState; } @@ -828,11 +646,11 @@ TriState Editor::selectionUnorderedListState() const TriState Editor::selectionOrderedListState() const { if (m_frame.selection().isCaret()) { - if (enclosingNodeWithTag(m_frame.selection().selection().start(), olTag)) + if (enclosingElementWithTag(m_frame.selection().selection().start(), olTag)) return TrueTriState; } else if (m_frame.selection().isRange()) { - Node* startNode = enclosingNodeWithTag(m_frame.selection().selection().start(), olTag); - Node* endNode = enclosingNodeWithTag(m_frame.selection().selection().end(), olTag); + auto* startNode = enclosingElementWithTag(m_frame.selection().selection().start(), olTag); + auto* endNode = enclosingElementWithTag(m_frame.selection().selection().end(), olTag); if (startNode && endNode && startNode == endNode) return TrueTriState; } @@ -870,34 +688,34 @@ bool Editor::canDecreaseSelectionListLevel() return canEditRichly() && DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(&document()); } -PassRefPtr<Node> Editor::increaseSelectionListLevel() +RefPtr<Node> Editor::increaseSelectionListLevel() { if (!canEditRichly() || m_frame.selection().isNone()) - return 0; + return nullptr; RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevel(&document()); revealSelectionAfterEditingOperation(); return newList; } -PassRefPtr<Node> Editor::increaseSelectionListLevelOrdered() +RefPtr<Node> Editor::increaseSelectionListLevelOrdered() { if (!canEditRichly() || m_frame.selection().isNone()) - return 0; + return nullptr; RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(&document()); revealSelectionAfterEditingOperation(); - return newList.release(); + return newList; } -PassRefPtr<Node> Editor::increaseSelectionListLevelUnordered() +RefPtr<Node> Editor::increaseSelectionListLevelUnordered() { if (!canEditRichly() || m_frame.selection().isNone()) - return 0; + return nullptr; RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(&document()); revealSelectionAfterEditingOperation(); - return newList.release(); + return newList; } void Editor::decreaseSelectionListLevel() @@ -916,38 +734,34 @@ void Editor::removeFormattingAndStyle() void Editor::clearLastEditCommand() { - m_lastEditCommand.clear(); + m_lastEditCommand = nullptr; } -#if PLATFORM(IOS) -// If the selection is adjusted from UIKit without closing the typing, the typing command may -// have a stale selection. -void Editor::ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping() -{ - TypingCommand::ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping(&m_frame, m_frame.selection().selection()); -} -#endif // Returns whether caller should continue with "the default processing", which is the same as // the event handler NOT setting the return value to false -bool Editor::dispatchCPPEvent(const AtomicString& eventType, ClipboardAccessPolicy policy) +bool Editor::dispatchCPPEvent(const AtomicString& eventType, DataTransferAccessPolicy policy) { Node* target = findEventTargetFromSelection(); if (!target) return true; - RefPtr<Clipboard> clipboard = Clipboard::createForCopyAndPaste(policy); + auto dataTransfer = DataTransfer::createForCopyAndPaste(policy); - RefPtr<Event> event = ClipboardEvent::create(eventType, true, true, clipboard); - target->dispatchEvent(event, IGNORE_EXCEPTION); + ClipboardEvent::Init init; + init.bubbles = true; + init.cancelable = true; + init.clipboardData = dataTransfer.ptr(); + auto event = ClipboardEvent::create(eventType, init, Event::IsTrusted::Yes); + target->dispatchEvent(event); bool noDefaultProcessing = event->defaultPrevented(); - if (noDefaultProcessing && policy == ClipboardWritable) { - OwnPtr<Pasteboard> pasteboard = Pasteboard::createForCopyAndPaste(); + if (noDefaultProcessing && policy == DataTransferAccessPolicy::Writable) { + auto pasteboard = Pasteboard::createForCopyAndPaste(); pasteboard->clear(); - pasteboard->writePasteboard(clipboard->pasteboard()); + pasteboard->writePasteboard(dataTransfer->pasteboard()); } - // invalidate clipboard here for security - clipboard->setAccessPolicy(ClipboardNumb); + // invalidate dataTransfer here for security + dataTransfer->setAccessPolicy(DataTransferAccessPolicy::Numb); return !noDefaultProcessing; } @@ -956,9 +770,9 @@ Node* Editor::findEventTargetFrom(const VisibleSelection& selection) const { Node* target = selection.start().element(); if (!target) - target = document().body(); + target = document().bodyOrFrameset(); if (!target) - return 0; + return nullptr; return target; } @@ -970,18 +784,39 @@ Node* Editor::findEventTargetFromSelection() const void Editor::applyStyle(StyleProperties* style, EditAction editingAction) { - switch (m_frame.selection().selectionType()) { - case VisibleSelection::NoSelection: - // do nothing - break; + if (style) + applyStyle(EditingStyle::create(style), editingAction); +} + +void Editor::applyStyle(RefPtr<EditingStyle>&& style, EditAction editingAction) +{ + if (!style) + return; + + auto selectionType = m_frame.selection().selection().selectionType(); + if (selectionType == VisibleSelection::NoSelection) + return; + + String inputTypeName = inputTypeNameForEditingAction(editingAction); + String inputEventData = inputEventDataForEditingStyleAndAction(*style, editingAction); + RefPtr<Element> element = m_frame.selection().selection().rootEditableElement(); + if (element && !dispatchBeforeInputEvent(*element, inputTypeName, inputEventData)) + return; + + switch (selectionType) { case VisibleSelection::CaretSelection: - computeAndSetTypingStyle(style, editingAction); + computeAndSetTypingStyle(*style, editingAction); break; case VisibleSelection::RangeSelection: - if (style) - applyCommand(ApplyStyleCommand::create(document(), EditingStyle::create(style).get(), editingAction)); + applyCommand(ApplyStyleCommand::create(document(), style.get(), editingAction)); + break; + default: break; } + + client()->didApplyStyle(); + if (element) + dispatchInputEvent(*element, inputTypeName, inputEventData); } bool Editor::shouldApplyStyle(StyleProperties* style, Range* range) @@ -991,16 +826,23 @@ bool Editor::shouldApplyStyle(StyleProperties* style, Range* range) void Editor::applyParagraphStyle(StyleProperties* style, EditAction editingAction) { - switch (m_frame.selection().selectionType()) { - case VisibleSelection::NoSelection: - // do nothing - break; - case VisibleSelection::CaretSelection: - case VisibleSelection::RangeSelection: - if (style) - applyCommand(ApplyStyleCommand::create(document(), EditingStyle::create(style).get(), editingAction, ApplyStyleCommand::ForceBlockProperties)); - break; - } + if (!style) + return; + + auto selectionType = m_frame.selection().selection().selectionType(); + if (selectionType == VisibleSelection::NoSelection) + return; + + String inputTypeName = inputTypeNameForEditingAction(editingAction); + String inputEventData = inputEventDataForEditingStyleAndAction(style, editingAction); + RefPtr<Element> element = m_frame.selection().selection().rootEditableElement(); + if (element && !dispatchBeforeInputEvent(*element, inputTypeName, inputEventData)) + return; + + applyCommand(ApplyStyleCommand::create(document(), EditingStyle::create(style).ptr(), editingAction, ApplyStyleCommand::ForceBlockProperties)); + client()->didApplyStyle(); + if (element) + dispatchInputEvent(*element, inputTypeName, inputEventData); } void Editor::applyStyleToSelection(StyleProperties* style, EditAction editingAction) @@ -1008,8 +850,21 @@ void Editor::applyStyleToSelection(StyleProperties* style, EditAction editingAct if (!style || style->isEmpty() || !canEditRichly()) return; - if (client() && client()->shouldApplyStyle(style, m_frame.selection().toNormalizedRange().get())) - applyStyle(style, editingAction); + if (!client() || !client()->shouldApplyStyle(style, m_frame.selection().toNormalizedRange().get())) + return; + applyStyle(style, editingAction); +} + +void Editor::applyStyleToSelection(Ref<EditingStyle>&& style, EditAction editingAction) +{ + if (style->isEmpty() || !canEditRichly()) + return; + + // FIXME: This is wrong for text decorations since m_mutableStyle is empty. + if (!client() || !client()->shouldApplyStyle(style->styleWithResolvedTextDecorations().ptr(), m_frame.selection().toNormalizedRange().get())) + return; + + applyStyle(WTFMove(style), editingAction); } void Editor::applyParagraphStyleToSelection(StyleProperties* style, EditAction editingAction) @@ -1054,93 +909,147 @@ void Editor::outdent() applyCommand(IndentOutdentCommand::create(document(), IndentOutdentCommand::Outdent)); } -static void dispatchEditableContentChangedEvents(PassRefPtr<Element> prpStartRoot, PassRefPtr<Element> prpEndRoot) +static void notifyTextFromControls(Element* startRoot, Element* endRoot) +{ + HTMLTextFormControlElement* startingTextControl = enclosingTextFormControl(firstPositionInOrBeforeNode(startRoot)); + HTMLTextFormControlElement* endingTextControl = enclosingTextFormControl(firstPositionInOrBeforeNode(endRoot)); + if (startingTextControl) + startingTextControl->didEditInnerTextValue(); + if (endingTextControl && startingTextControl != endingTextControl) + endingTextControl->didEditInnerTextValue(); +} + +static bool dispatchBeforeInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomicString& inputTypeName, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }, bool cancelable = true) { - RefPtr<Element> startRoot = prpStartRoot; - RefPtr<Element> endRoot = prpEndRoot; + bool continueWithDefaultBehavior = true; if (startRoot) - startRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false), IGNORE_EXCEPTION); + continueWithDefaultBehavior &= dispatchBeforeInputEvent(*startRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges, cancelable); if (endRoot && endRoot != startRoot) - endRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false), IGNORE_EXCEPTION); + continueWithDefaultBehavior &= dispatchBeforeInputEvent(*endRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges, cancelable); + return continueWithDefaultBehavior; +} + +static void dispatchInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomicString& inputTypeName, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }) +{ + if (startRoot) + dispatchInputEvent(*startRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges); + if (endRoot && endRoot != startRoot) + dispatchInputEvent(*endRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges); +} + +bool Editor::willApplyEditing(CompositeEditCommand& command, Vector<RefPtr<StaticRange>>&& targetRanges) const +{ + if (!command.shouldDispatchInputEvents()) + return true; + + auto* composition = command.composition(); + if (!composition) + return true; + + return dispatchBeforeInputEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement(), command.inputEventTypeName(), command.inputEventData(), command.inputEventDataTransfer(), targetRanges, command.isBeforeInputEventCancelable()); } void Editor::appliedEditing(PassRefPtr<CompositeEditCommand> cmd) { + LOG(Editing, "Editor %p appliedEditing", this); + document().updateLayout(); EditCommandComposition* composition = cmd->composition(); ASSERT(composition); VisibleSelection newSelection(cmd->endingSelection()); - m_alternativeTextController->respondToAppliedEditing(cmd.get()); + notifyTextFromControls(composition->startingRootEditableElement(), composition->endingRootEditableElement()); - // Don't clear the typing style with this selection change. We do those things elsewhere if necessary. - FrameSelection::SetSelectionOptions options = cmd->isDictationCommand() ? FrameSelection::DictationTriggered : 0; - changeSelectionAfterCommand(newSelection, options); - dispatchEditableContentChangedEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement()); + if (cmd->isTopLevelCommand()) { + // Don't clear the typing style with this selection change. We do those things elsewhere if necessary. + FrameSelection::SetSelectionOptions options = cmd->isDictationCommand() ? FrameSelection::DictationTriggered : 0; - if (!cmd->preservesTypingStyle()) - m_frame.selection().clearTypingStyle(); + changeSelectionAfterCommand(newSelection, options); + } - // Command will be equal to last edit command only in the case of typing - if (m_lastEditCommand.get() == cmd) - ASSERT(cmd->isTypingCommand()); - else { - // Only register a new undo command if the command passed in is - // different from the last command - m_lastEditCommand = cmd; - if (client()) - client()->registerUndoStep(m_lastEditCommand->ensureComposition()); + if (cmd->shouldDispatchInputEvents()) + dispatchInputEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement(), cmd->inputEventTypeName(), cmd->inputEventData(), cmd->inputEventDataTransfer()); + + if (cmd->isTopLevelCommand()) { + updateEditorUINowIfScheduled(); + + m_alternativeTextController->respondToAppliedEditing(cmd.get()); + + if (!cmd->preservesTypingStyle()) + m_frame.selection().clearTypingStyle(); + + // Command will be equal to last edit command only in the case of typing + if (m_lastEditCommand.get() == cmd) + ASSERT(cmd->isTypingCommand()); + else { + // Only register a new undo command if the command passed in is + // different from the last command + m_lastEditCommand = cmd; + if (client()) + client()->registerUndoStep(m_lastEditCommand->ensureComposition()); + } + respondToChangedContents(newSelection); } +} - respondToChangedContents(newSelection); +bool Editor::willUnapplyEditing(const EditCommandComposition& composition) const +{ + return dispatchBeforeInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyUndo"); } -void Editor::unappliedEditing(PassRefPtr<EditCommandComposition> cmd) +void Editor::unappliedEditing(EditCommandComposition& composition) { document().updateLayout(); - VisibleSelection newSelection(cmd->startingSelection()); - changeSelectionAfterCommand(newSelection, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle); - dispatchEditableContentChangedEvents(cmd->startingRootEditableElement(), cmd->endingRootEditableElement()); + notifyTextFromControls(composition.startingRootEditableElement(), composition.endingRootEditableElement()); - m_alternativeTextController->respondToUnappliedEditing(cmd.get()); + VisibleSelection newSelection(composition.startingSelection()); + changeSelectionAfterCommand(newSelection, FrameSelection::defaultSetSelectionOptions()); + dispatchInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyUndo"); - m_lastEditCommand = 0; - if (client()) - client()->registerRedoStep(cmd); + updateEditorUINowIfScheduled(); + + m_alternativeTextController->respondToUnappliedEditing(&composition); + + m_lastEditCommand = nullptr; + if (auto* client = this->client()) + client->registerRedoStep(composition); respondToChangedContents(newSelection); } -void Editor::reappliedEditing(PassRefPtr<EditCommandComposition> cmd) +bool Editor::willReapplyEditing(const EditCommandComposition& composition) const +{ + return dispatchBeforeInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyRedo"); +} + +void Editor::reappliedEditing(EditCommandComposition& composition) { document().updateLayout(); - VisibleSelection newSelection(cmd->endingSelection()); - changeSelectionAfterCommand(newSelection, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle); - dispatchEditableContentChangedEvents(cmd->startingRootEditableElement(), cmd->endingRootEditableElement()); + notifyTextFromControls(composition.startingRootEditableElement(), composition.endingRootEditableElement()); - m_lastEditCommand = 0; - if (client()) - client()->registerUndoStep(cmd); + VisibleSelection newSelection(composition.endingSelection()); + changeSelectionAfterCommand(newSelection, FrameSelection::defaultSetSelectionOptions()); + dispatchInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyRedo"); + + updateEditorUINowIfScheduled(); + + m_lastEditCommand = nullptr; + if (auto* client = this->client()) + client->registerUndoStep(composition); respondToChangedContents(newSelection); } Editor::Editor(Frame& frame) : m_frame(frame) -#if ENABLE(DELETION_UI) - , m_deleteButtonController(adoptPtr(new DeleteButtonController(frame))) + , m_killRing(std::make_unique<KillRing>()) + , m_spellChecker(std::make_unique<SpellChecker>(frame)) + , m_alternativeTextController(std::make_unique<AlternativeTextController>(frame)) + , m_editorUIUpdateTimer(*this, &Editor::editorUIUpdateTimerFired) +#if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS) + , m_telephoneNumberDetectionUpdateTimer(*this, &Editor::scanSelectionForTelephoneNumbers) #endif - , m_ignoreCompositionSelectionChange(false) - , m_shouldStartNewKillRingSequence(false) - // This is off by default, since most editors want this behavior (this matches IE but not FF). - , m_shouldStyleWithCSS(false) - , m_killRing(adoptPtr(new KillRing)) - , m_spellChecker(adoptPtr(new SpellChecker(frame))) - , m_alternativeTextController(adoptPtr(new AlternativeTextController(frame))) - , m_areMarkedTextMatchesHighlighted(false) - , m_defaultParagraphSeparator(EditorParagraphSeparatorIsDiv) - , m_overwriteModeEnabled(false) { } @@ -1150,19 +1059,19 @@ Editor::~Editor() void Editor::clear() { - m_compositionNode = 0; + if (m_compositionNode) { + m_compositionNode = nullptr; + if (EditorClient* client = this->client()) + client->discardedComposition(&m_frame); + } m_customCompositionUnderlines.clear(); m_shouldStyleWithCSS = false; m_defaultParagraphSeparator = EditorParagraphSeparatorIsDiv; - -#if ENABLE(DELETION_UI) - m_deleteButtonController = adoptPtr(new DeleteButtonController(m_frame)); -#endif } -bool Editor::insertText(const String& text, Event* triggeringEvent) +bool Editor::insertText(const String& text, Event* triggeringEvent, TextEventInputType inputType) { - return m_frame.eventHandler().handleTextInputEvent(text, triggeringEvent); + return m_frame.eventHandler().handleTextInputEvent(text, triggeringEvent, inputType); } bool Editor::insertTextForConfirmedComposition(const String& text) @@ -1185,7 +1094,7 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn return false; RefPtr<Range> range = selection.toNormalizedRange(); - if (!shouldInsertText(text, range.get(), EditorInsertActionTyped)) + if (!shouldInsertText(text, range.get(), EditorInsertAction::Typed)) return true; updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0])); @@ -1209,20 +1118,28 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn // Insert the text if (triggeringEvent && triggeringEvent->isDictation()) - DictationCommand::insertText(&document.get(), text, triggeringEvent->dictationAlternatives(), selection); + DictationCommand::insertText(document, 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); + if (triggeringEvent && triggeringEvent->isAutocompletion()) + options |= TypingCommand::IsAutocompletion; + TypingCommand::insertText(document, text, selection, options, triggeringEvent && triggeringEvent->isComposition() ? TypingCommand::TextCompositionFinal : TypingCommand::TextCompositionNone); } // Reveal the current selection if (Frame* editedFrame = document->frame()) - if (Page* page = editedFrame->page()) - page->focusController().focusedOrMainFrame().selection().revealSelection(ScrollAlignment::alignCenterIfNeeded); + if (Page* page = editedFrame->page()) { +#if PLATFORM(IOS) + SelectionRevealMode revealMode = SelectionRevealMode::RevealUpToMainFrame; +#else + SelectionRevealMode revealMode = SelectionRevealMode::Reveal; +#endif + page->focusController().focusedOrMainFrame().selection().revealSelection(revealMode, ScrollAlignment::alignCenterIfNeeded); + } } } @@ -1234,7 +1151,7 @@ bool Editor::insertLineBreak() if (!canEdit()) return false; - if (!shouldInsertText("\n", m_frame.selection().toNormalizedRange().get(), EditorInsertActionTyped)) + if (!shouldInsertText("\n", m_frame.selection().toNormalizedRange().get(), EditorInsertAction::Typed)) return true; VisiblePosition caret = m_frame.selection().selection().visibleStart(); @@ -1254,7 +1171,7 @@ bool Editor::insertParagraphSeparator() if (!canEditRichly()) return insertLineBreak(); - if (!shouldInsertText("\n", m_frame.selection().toNormalizedRange().get(), EditorInsertActionTyped)) + if (!shouldInsertText("\n", m_frame.selection().toNormalizedRange().get(), EditorInsertAction::Typed)) return true; VisiblePosition caret = m_frame.selection().selection().visibleStart(); @@ -1266,6 +1183,14 @@ bool Editor::insertParagraphSeparator() return true; } +bool Editor::insertParagraphSeparatorInQuotedContent() +{ + // FIXME: Why is this missing calls to canEdit, canEditRichly, etc.? + TypingCommand::insertParagraphSeparatorInQuotedContent(document()); + revealSelectionAfterEditingOperation(); + return true; +} + void Editor::cut() { if (tryDHTMLCut()) @@ -1275,24 +1200,7 @@ void Editor::cut() return; } - // FIXME: This should share more code with the copy function; there is a lot of overlap. - RefPtr<Range> selection = selectedRange(); - willWriteSelectionToPasteboard(selection); - if (shouldDeleteRange(selection.get())) { - updateMarkersForWordsAffectedByEditing(true); - if (enclosingTextFormControl(m_frame.selection().start())) - Pasteboard::createForCopyAndPaste()->writePlainText(selectedTextForClipboard(), canSmartCopyOrDelete() ? Pasteboard::CanSmartReplace : Pasteboard::CannotSmartReplace); - else { -#if PLATFORM(MAC) || PLATFORM(EFL) - writeSelectionToPasteboard(*Pasteboard::createForCopyAndPaste()); -#else - // FIXME: Convert all other platforms to match Mac and delete this. - Pasteboard::createForCopyAndPaste()->writeSelection(*selection, canSmartCopyOrDelete(), m_frame, IncludeImageAltTextForClipboard); -#endif - } - didWriteSelectionToPasteboard(); - deleteSelectionWithSmartDelete(canSmartCopyOrDelete()); - } + performCutOrCopy(CutAction); } void Editor::copy() @@ -1304,28 +1212,64 @@ void Editor::copy() return; } - willWriteSelectionToPasteboard(selectedRange()); - if (enclosingTextFormControl(m_frame.selection().start())) { - Pasteboard::createForCopyAndPaste()->writePlainText(selectedTextForClipboard(), - canSmartCopyOrDelete() ? Pasteboard::CanSmartReplace : Pasteboard::CannotSmartReplace); - } else { - if (HTMLImageElement* imageElement = imageElementFromImageDocument(document())) { -#if PLATFORM(MAC) || PLATFORM(EFL) + performCutOrCopy(CopyAction); +} + +void Editor::postTextStateChangeNotificationForCut(const String& text, const VisibleSelection& selection) +{ + if (!AXObjectCache::accessibilityEnabled()) + return; + if (!text.length()) + return; + AXObjectCache* cache = document().existingAXObjectCache(); + if (!cache) + return; + cache->postTextStateChangeNotification(selection.start().anchorNode(), AXTextEditTypeCut, text, selection.start()); +} + +void Editor::performCutOrCopy(EditorActionSpecifier action) +{ + RefPtr<Range> selection = selectedRange(); + willWriteSelectionToPasteboard(selection); + if (action == CutAction) { + if (!shouldDeleteRange(selection.get())) + return; + + updateMarkersForWordsAffectedByEditing(true); + } + + if (enclosingTextFormControl(m_frame.selection().selection().start())) + Pasteboard::createForCopyAndPaste()->writePlainText(selectedTextForDataTransfer(), canSmartCopyOrDelete() ? Pasteboard::CanSmartReplace : Pasteboard::CannotSmartReplace); + else { + HTMLImageElement* imageElement = nullptr; + if (action == CopyAction) + imageElement = imageElementFromImageDocument(document()); + + if (imageElement) { +#if PLATFORM(COCOA) || PLATFORM(GTK) writeImageToPasteboard(*Pasteboard::createForCopyAndPaste(), *imageElement, document().url(), document().title()); #else Pasteboard::createForCopyAndPaste()->writeImage(*imageElement, document().url(), document().title()); #endif } else { -#if PLATFORM(MAC) || PLATFORM(EFL) +#if PLATFORM(COCOA) || PLATFORM(GTK) writeSelectionToPasteboard(*Pasteboard::createForCopyAndPaste()); #else // FIXME: Convert all other platforms to match Mac and delete this. - Pasteboard::createForCopyAndPaste()->writeSelection(*selectedRange(), canSmartCopyOrDelete(), m_frame, IncludeImageAltTextForClipboard); + Pasteboard::createForCopyAndPaste()->writeSelection(*selection, canSmartCopyOrDelete(), m_frame, IncludeImageAltTextForDataTransfer); #endif } } didWriteSelectionToPasteboard(); + if (action == CutAction) { + String text; + if (AXObjectCache::accessibilityEnabled()) + text = AccessibilityObject::stringForVisiblePositionRange(m_frame.selection().selection()); + deleteSelectionWithSmartDelete(canSmartCopyOrDelete(), EditActionCut); + if (AXObjectCache::accessibilityEnabled()) + postTextStateChangeNotificationForCut(text, m_frame.selection().selection()); + } } void Editor::paste() @@ -1340,9 +1284,8 @@ void Editor::paste(Pasteboard& pasteboard) if (!canPaste()) return; updateMarkersForWordsAffectedByEditing(false); - CachedResourceLoader* loader = document().cachedResourceLoader(); - ResourceCacheValidationSuppressor validationSuppressor(loader); - if (m_frame.selection().isContentRichlyEditable()) + ResourceCacheValidationSuppressor validationSuppressor(document().cachedResourceLoader()); + if (m_frame.selection().selection().isContentRichlyEditable()) pasteWithPasteboard(&pasteboard, true); else pasteAsPlainTextWithPasteboard(pasteboard); @@ -1365,7 +1308,7 @@ void Editor::performDelete() return; } - addToKillRing(selectedRange().get(), false); + addRangeToKillRing(*selectedRange().get(), KillRingInsertionMode::AppendText); deleteSelectionWithSmartDelete(canSmartCopyOrDelete()); // clear the "start new kill ring sequence" setting, because it was set to true @@ -1383,12 +1326,12 @@ void Editor::simplifyMarkup(Node* startNode, Node* endNode) // check if start node is before endNode Node* node = startNode; while (node && node != endNode) - node = NodeTraversal::next(node); + node = NodeTraversal::next(*node); if (!node) return; } - applyCommand(SimplifyMarkupCommand::create(document(), startNode, (endNode) ? NodeTraversal::next(endNode) : 0)); + applyCommand(SimplifyMarkupCommand::create(document(), startNode, endNode ? NodeTraversal::next(*endNode) : nullptr)); } void Editor::copyURL(const URL& url, const String& title) @@ -1402,7 +1345,7 @@ void Editor::copyURL(const URL& url, const String& title, Pasteboard& pasteboard pasteboardURL.url = url; pasteboardURL.title = title; -#if PLATFORM(MAC) && !PLATFORM(IOS) +#if PLATFORM(MAC) fillInUserVisibleForm(pasteboardURL); #endif @@ -1410,6 +1353,7 @@ void Editor::copyURL(const URL& url, const String& title, Pasteboard& pasteboard } #if !PLATFORM(IOS) + void Editor::copyImage(const HitTestResult& result) { Element* element = result.innerNonSharedElement(); @@ -1420,12 +1364,13 @@ void Editor::copyImage(const HitTestResult& result) if (url.isEmpty()) url = result.absoluteImageURL(); -#if PLATFORM(MAC) || PLATFORM(EFL) +#if PLATFORM(COCOA) || PLATFORM(GTK) writeImageToPasteboard(*Pasteboard::createForCopyAndPaste(), *element, url, result.altDisplayString()); #else Pasteboard::createForCopyAndPaste()->writeImage(*element, url, result.altDisplayString()); #endif } + #endif bool Editor::isContinuousSpellCheckingEnabled() const @@ -1644,11 +1589,18 @@ void Editor::setBaseWritingDirection(WritingDirection direction) #endif Element* focusedElement = document().focusedElement(); - if (focusedElement && isHTMLTextFormControlElement(*focusedElement)) { + if (is<HTMLTextFormControlElement>(focusedElement)) { if (direction == NaturalWritingDirection) return; - toHTMLElement(focusedElement)->setAttribute(dirAttr, direction == LeftToRightWritingDirection ? "ltr" : "rtl"); - focusedElement->dispatchInputEvent(); + + auto& focusedFormElement = downcast<HTMLTextFormControlElement>(*focusedElement); + auto directionValue = direction == LeftToRightWritingDirection ? "ltr" : "rtl"; + auto writingDirectionInputTypeName = inputTypeNameForEditingAction(EditActionSetWritingDirection); + if (!dispatchBeforeInputEvent(focusedFormElement, writingDirectionInputTypeName, directionValue)) + return; + + focusedFormElement.setAttributeWithoutSynchronization(dirAttr, directionValue); + dispatchInputEvent(focusedFormElement, writingDirectionInputTypeName, directionValue); document().updateStyleIfNeeded(); return; } @@ -1742,29 +1694,25 @@ void Editor::setComposition(const String& text, SetCompositionMode mode) else selectComposition(); + m_compositionNode = nullptr; + m_customCompositionUnderlines.clear(); + if (m_frame.selection().isNone()) { setIgnoreCompositionSelectionChange(false); return; } - - // Dispatch a compositionend event to the focused node. - // We should send this event before sending a TextEvent as written in Section 6.2.2 and 6.2.3 of - // the DOM Event specification. - if (Element* target = document().focusedElement()) { - RefPtr<CompositionEvent> event = CompositionEvent::create(eventNames().compositionendEvent, document().domWindow(), text); - target->dispatchEvent(event.release(), IGNORE_EXCEPTION); - } - - // 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() && mode != CancelComposition) - TypingCommand::deleteSelection(document(), 0); - m_compositionNode = 0; - m_customCompositionUnderlines.clear(); + // Always delete the current composition before inserting the finalized composition text if we're confirming our composition. + // Our default behavior (if the beforeinput event is not prevented) is to insert the finalized composition text back in. + // We pass TypingCommand::TextCompositionPending here to indicate that we are deleting the pending composition. + if (mode != CancelComposition) + TypingCommand::deleteSelection(document(), 0, TypingCommand::TextCompositionPending); insertTextForConfirmedComposition(text); + if (auto* target = document().focusedElement()) + target->dispatchEvent(CompositionEvent::create(eventNames().compositionendEvent, document().domWindow(), text)); + if (mode == CancelComposition) { // An open typing command that disagrees about current selection would cause issues with typing later on. TypingCommand::closeTyping(&m_frame); @@ -1775,6 +1723,8 @@ void Editor::setComposition(const String& text, SetCompositionMode mode) void Editor::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd) { + Ref<Frame> protection(m_frame); + UserTypingGestureIndicator typingGestureIndicator(m_frame); setIgnoreCompositionSelectionChange(true); @@ -1791,6 +1741,18 @@ void Editor::setComposition(const String& text, const Vector<CompositionUnderlin return; } + String originalText = selectedText(); + bool isStartingToRecomposeExistingRange = !text.isEmpty() && selectionStart < selectionEnd && !hasComposition(); + if (isStartingToRecomposeExistingRange) { + // We pass TypingCommand::TextCompositionFinal here to indicate that we are removing composition text that has been finalized. + TypingCommand::deleteSelection(document(), 0, TypingCommand::TextCompositionFinal); + const VisibleSelection& currentSelection = m_frame.selection().selection(); + if (currentSelection.isRange()) { + // If deletion was prevented, then we need to collapse the selection to the end so that the original text will not be recomposed. + m_frame.selection().setSelection({ currentSelection.end(), currentSelection.end() }); + } + } + #if PLATFORM(IOS) client()->startDelayingAndCoalescingContentChangeNotifications(); #endif @@ -1817,47 +1779,46 @@ void Editor::setComposition(const String& text, const Vector<CompositionUnderlin // We should send a compositionstart event only when the given text is not empty because this // function doesn't create a composition node when the text is empty. if (!text.isEmpty()) { - target->dispatchEvent(CompositionEvent::create(eventNames().compositionstartEvent, document().domWindow(), selectedText())); + target->dispatchEvent(CompositionEvent::create(eventNames().compositionstartEvent, document().domWindow(), originalText)); event = CompositionEvent::create(eventNames().compositionupdateEvent, document().domWindow(), text); } - } else { - if (!text.isEmpty()) - event = CompositionEvent::create(eventNames().compositionupdateEvent, document().domWindow(), text); - else - event = CompositionEvent::create(eventNames().compositionendEvent, document().domWindow(), text); - } - if (event.get()) - target->dispatchEvent(event, IGNORE_EXCEPTION); + } else if (!text.isEmpty()) + event = CompositionEvent::create(eventNames().compositionupdateEvent, document().domWindow(), text); + + if (event) + target->dispatchEvent(*event); } // 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()) - TypingCommand::deleteSelection(document(), TypingCommand::PreventSpellChecking); + if (text.isEmpty()) { + TypingCommand::deleteSelection(document(), TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionPending); + if (target) + target->dispatchEvent(CompositionEvent::create(eventNames().compositionendEvent, document().domWindow(), text)); + } - m_compositionNode = 0; + m_compositionNode = nullptr; m_customCompositionUnderlines.clear(); if (!text.isEmpty()) { - TypingCommand::insertText(document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate); + TypingCommand::insertText(document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionPending); // Find out what node has the composition now. - Position base = m_frame.selection().base().downstream(); - Position extent = m_frame.selection().extent(); + Position base = m_frame.selection().selection().base().downstream(); + Position extent = m_frame.selection().selection().extent(); Node* baseNode = base.deprecatedNode(); unsigned baseOffset = base.deprecatedEditingOffset(); Node* extentNode = extent.deprecatedNode(); unsigned extentOffset = extent.deprecatedEditingOffset(); - if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) { - m_compositionNode = toText(baseNode); + if (is<Text>(baseNode) && baseNode == extentNode && baseOffset + text.length() == extentOffset) { + m_compositionNode = downcast<Text>(baseNode); m_compositionStart = baseOffset; m_compositionEnd = extentOffset; m_customCompositionUnderlines = underlines; - size_t numUnderlines = m_customCompositionUnderlines.size(); - for (size_t i = 0; i < numUnderlines; ++i) { - m_customCompositionUnderlines[i].startOffset += baseOffset; - m_customCompositionUnderlines[i].endOffset += baseOffset; + for (auto& underline : m_customCompositionUnderlines) { + underline.startOffset += baseOffset; + underline.endOffset += baseOffset; } if (baseNode->renderer()) baseNode->renderer()->repaint(); @@ -1907,8 +1868,11 @@ void Editor::learnSpelling() } #if !PLATFORM(IOS) + void Editor::advanceToNextMisspelling(bool startBeforeSelection) { + Ref<Frame> protection(m_frame); + // The basic approach is to search in two phases - from the selection end to the end of the doc, and // then we wrap and search from the doc start to (approximately) where we started. @@ -1937,19 +1901,21 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) // when spell checking the whole document before sending the message. // In that case the document might not be editable, but there are editable pockets that need to be spell checked. - position = firstEditablePositionAfterPositionInRoot(position, document().documentElement()).deepEquivalent(); + position = VisiblePosition(firstEditablePositionAfterPositionInRoot(position, document().documentElement())).deepEquivalent(); if (position.isNull()) return; Position rangeCompliantPosition = position.parentAnchoredEquivalent(); - spellingSearchRange->setStart(rangeCompliantPosition.deprecatedNode(), rangeCompliantPosition.deprecatedEditingOffset(), IGNORE_EXCEPTION); + if (rangeCompliantPosition.deprecatedNode()) + spellingSearchRange->setStart(*rangeCompliantPosition.deprecatedNode(), rangeCompliantPosition.deprecatedEditingOffset()); startedWithSelection = false; // won't need to wrap } // topNode defines the whole range we want to operate on - Node* topNode = highestEditableRoot(position); + auto* topNode = highestEditableRoot(position); // FIXME: lastOffsetForEditing() is wrong here if editingIgnoresContent(highestEditableRoot()) returns true (e.g. a <table>) - spellingSearchRange->setEnd(topNode, lastOffsetForEditing(topNode), IGNORE_EXCEPTION); + if (topNode) + spellingSearchRange->setEnd(*topNode, lastOffsetForEditing(*topNode)); // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking // at a word boundary. Going back by one char and then forward by a word does the trick. @@ -1960,7 +1926,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) // else we were already at the start of the editable node } - if (spellingSearchRange->collapsed(IGNORE_EXCEPTION)) + if (spellingSearchRange->collapsed()) return; // nothing to search in // Get the spell checker if it is available @@ -1970,7 +1936,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) // We go to the end of our first range instead of the start of it, just to be sure // we don't get foiled by any word boundary problems at the start. It means we might // do a tiny bit more searching. - Node* searchEndNodeAfterWrap = spellingSearchRange->endContainer(); + Node& searchEndNodeAfterWrap = spellingSearchRange->endContainer(); int searchEndOffsetAfterWrap = spellingSearchRange->endOffset(); int misspellingOffset = 0; @@ -1985,7 +1951,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) String foundItem; RefPtr<Range> firstMisspellingRange; if (unifiedTextCheckerEnabled()) { - grammarSearchRange = spellingSearchRange->cloneRange(IGNORE_EXCEPTION); + grammarSearchRange = spellingSearchRange->cloneRange(); foundItem = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { misspelledWord = foundItem; @@ -1998,12 +1964,12 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) misspelledWord = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); #if USE(GRAMMAR_CHECKING) - grammarSearchRange = spellingSearchRange->cloneRange(IGNORE_EXCEPTION); + grammarSearchRange = spellingSearchRange->cloneRange(); if (!misspelledWord.isEmpty()) { // Stop looking at start of next misspelled word - CharacterIterator chars(grammarSearchRange.get()); + CharacterIterator chars(*grammarSearchRange); chars.advance(misspellingOffset); - grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION); + grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset()); } if (isGrammarCheckingEnabled()) @@ -2014,12 +1980,13 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) // If we found neither bad grammar nor a misspelled word, wrap and try again (but don't bother if we started at the beginning of the // block rather than at a selection). if (startedWithSelection && !misspelledWord && !badGrammarPhrase) { - spellingSearchRange->setStart(topNode, 0, IGNORE_EXCEPTION); + if (topNode) + spellingSearchRange->setStart(*topNode, 0); // going until the end of the very first chunk we tested is far enough - spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap, IGNORE_EXCEPTION); + spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap); if (unifiedTextCheckerEnabled()) { - grammarSearchRange = spellingSearchRange->cloneRange(IGNORE_EXCEPTION); + grammarSearchRange = spellingSearchRange->cloneRange(); foundItem = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { misspelledWord = foundItem; @@ -2032,12 +1999,12 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) misspelledWord = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); #if USE(GRAMMAR_CHECKING) - grammarSearchRange = spellingSearchRange->cloneRange(IGNORE_EXCEPTION); + grammarSearchRange = spellingSearchRange->cloneRange(); if (!misspelledWord.isEmpty()) { // Stop looking at start of next misspelled word - CharacterIterator chars(grammarSearchRange.get()); + CharacterIterator chars(*grammarSearchRange); chars.advance(misspellingOffset); - grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION); + grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset()); } if (isGrammarCheckingEnabled()) @@ -2060,7 +2027,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + grammarDetail.location, grammarDetail.length); - m_frame.selection().setSelection(VisibleSelection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY)); + m_frame.selection().setSelection(VisibleSelection(*badGrammarRange, SEL_DEFAULT_AFFINITY)); m_frame.selection().revealSelection(); client()->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail); @@ -2072,13 +2039,14 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) // a marker so we draw the red squiggle later. RefPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, misspelledWord.length()); - m_frame.selection().setSelection(VisibleSelection(misspellingRange.get(), DOWNSTREAM)); + m_frame.selection().setSelection(VisibleSelection(*misspellingRange, DOWNSTREAM)); m_frame.selection().revealSelection(); client()->updateSpellingUIWithMisspelledWord(misspelledWord); document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling); } } + #endif // !PLATFORM(IOS) String Editor::misspelledWordAtCaretOrRange(Node* clickedNode) const @@ -2106,7 +2074,7 @@ String Editor::misspelledWordAtCaretOrRange(Node* clickedNode) const int wordLength = word.length(); int misspellingLocation = -1; int misspellingLength = 0; - textChecker()->checkSpellingOfString(word.deprecatedCharacters(), wordLength, &misspellingLocation, &misspellingLength); + textChecker()->checkSpellingOfString(word, &misspellingLocation, &misspellingLength); return misspellingLength == wordLength ? word : String(); } @@ -2120,7 +2088,7 @@ String Editor::misspelledSelectionString() const int misspellingLocation = -1; int misspellingLength = 0; - textChecker()->checkSpellingOfString(selectedString.deprecatedCharacters(), length, &misspellingLocation, &misspellingLength); + textChecker()->checkSpellingOfString(selectedString, &misspellingLocation, &misspellingLength); // The selection only counts as misspelled if the selected text is exactly one misspelled word if (misspellingLength != length) @@ -2153,7 +2121,7 @@ Vector<String> Editor::guessesForMisspelledWord(const String& word) const Vector<String> guesses; if (client()) - textChecker()->getGuessesForWord(word, String(), guesses); + textChecker()->getGuessesForWord(word, String(), m_frame.selection().selection(), guesses); return guesses; } @@ -2161,13 +2129,13 @@ Vector<String> Editor::guessesForMisspelledOrUngrammatical(bool& misspelled, boo { if (unifiedTextCheckerEnabled()) { RefPtr<Range> range; - FrameSelection& frameSelection = m_frame.selection(); - if (frameSelection.isCaret() && behavior().shouldAllowSpellingSuggestionsWithoutSelection()) { - VisibleSelection wordSelection = VisibleSelection(frameSelection.base()); + VisibleSelection selection = m_frame.selection().selection(); + if (selection.isCaret() && behavior().shouldAllowSpellingSuggestionsWithoutSelection()) { + VisibleSelection wordSelection = VisibleSelection(selection.base()); wordSelection.expandUsingGranularity(WordGranularity); range = wordSelection.toNormalizedRange(); } else - range = frameSelection.toNormalizedRange(); + range = selection.toNormalizedRange(); if (!range) return Vector<String>(); return TextCheckingHelper(client(), range).guessesForMisspelledOrUngrammaticalRange(isGrammarCheckingEnabled(), misspelled, ungrammatical); @@ -2224,6 +2192,8 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelecti void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping, bool doReplacement) { + Ref<Frame> protection(m_frame); + #if PLATFORM(IOS) UNUSED_PARAM(selectionAfterTyping); UNUSED_PARAM(doReplacement); @@ -2289,19 +2259,19 @@ void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, // If autocorrected word is non empty, replace the misspelled word by this word. if (!autocorrectedString.isEmpty()) { - VisibleSelection newSelection(misspellingRange.get(), DOWNSTREAM); + VisibleSelection newSelection(*misspellingRange, DOWNSTREAM); if (newSelection != m_frame.selection().selection()) { if (!m_frame.selection().shouldChangeSelection(newSelection)) return; m_frame.selection().setSelection(newSelection); } - if (!m_frame.editor().shouldInsertText(autocorrectedString, misspellingRange.get(), EditorInsertActionTyped)) + if (!m_frame.editor().shouldInsertText(autocorrectedString, misspellingRange.get(), EditorInsertAction::Typed)) return; - m_frame.editor().replaceSelectionWithText(autocorrectedString, false, false); + m_frame.editor().replaceSelectionWithText(autocorrectedString, false, false, EditActionInsert); // Reset the charet one character further. - m_frame.selection().moveTo(m_frame.selection().end()); + m_frame.selection().moveTo(m_frame.selection().selection().end()); m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity); } @@ -2329,11 +2299,11 @@ void Editor::markMisspellingsOrBadGrammar(const VisibleSelection& selection, boo return; // If we're not in an editable node, bail. - Node* editableNode = searchRange->startContainer(); - if (!editableNode || !editableNode->hasEditableStyle()) + Node& editableNode = searchRange->startContainer(); + if (!editableNode.hasEditableStyle()) return; - if (!isSpellCheckingEnabledFor(editableNode)) + if (!isSpellCheckingEnabledFor(&editableNode)) return; // Get the spell checker if it is available @@ -2362,15 +2332,19 @@ bool Editor::isSpellCheckingEnabledFor(Node* node) const { if (!node) return false; - const Element* focusedElement = node->isElementNode() ? toElement(node) : node->parentElement(); - if (!focusedElement) + Element* element = is<Element>(*node) ? downcast<Element>(node) : node->parentElement(); + if (!element) return false; - return focusedElement->isSpellCheckingEnabled(); + if (element->isInUserAgentShadowTree()) { + if (HTMLTextFormControlElement* textControl = enclosingTextFormControl(firstPositionInOrBeforeNode(element))) + return textControl->isSpellCheckingEnabled(); + } + return element->isSpellCheckingEnabled(); } bool Editor::isSpellCheckingEnabledInFocusedNode() const { - return isSpellCheckingEnabledFor(m_frame.selection().start().deprecatedNode()); + return isSpellCheckingEnabledFor(m_frame.selection().selection().start().deprecatedNode()); } void Editor::markMisspellings(const VisibleSelection& selection, RefPtr<Range>& firstMisspellingRange) @@ -2403,33 +2377,33 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textC return; // If we're not in an editable node, bail. - Node* editableNode = spellingRange->startContainer(); - if (!editableNode || !editableNode->hasEditableStyle()) + Node& editableNode = spellingRange->startContainer(); + if (!editableNode.hasEditableStyle()) return; - if (!isSpellCheckingEnabledFor(editableNode)) + if (!isSpellCheckingEnabledFor(&editableNode)) return; Range* rangeToCheck = shouldMarkGrammar ? grammarRange : spellingRange; TextCheckingParagraph paragraphToCheck(rangeToCheck); - if (paragraphToCheck.isRangeEmpty() || paragraphToCheck.isEmpty()) + if (paragraphToCheck.isEmpty()) return; RefPtr<Range> paragraphRange = paragraphToCheck.paragraphRange(); bool asynchronous = m_frame.settings().asynchronousSpellCheckingEnabled() && !shouldShowCorrectionPanel; // In asynchronous mode, we intentionally check paragraph-wide sentence. - RefPtr<SpellCheckRequest> request = SpellCheckRequest::create(resolveTextCheckingTypeMask(textCheckingOptions), TextCheckingProcessIncremental, asynchronous ? paragraphRange : rangeToCheck, paragraphRange); + const auto resolvedOptions = resolveTextCheckingTypeMask(editableNode, textCheckingOptions); + auto request = SpellCheckRequest::create(resolvedOptions, TextCheckingProcessIncremental, asynchronous ? paragraphRange : rangeToCheck, paragraphRange); if (asynchronous) { - m_spellChecker->requestCheckingFor(request); + m_spellChecker->requestCheckingFor(WTFMove(request)); return; } Vector<TextCheckingResult> results; - checkTextOfParagraph(textChecker(), paragraphToCheck.textDeprecatedCharacters(), paragraphToCheck.textLength(), - resolveTextCheckingTypeMask(textCheckingOptions), results); - markAndReplaceFor(request, results); + checkTextOfParagraph(*textChecker(), paragraphToCheck.text(), resolvedOptions, results, m_frame.selection().selection()); + markAndReplaceFor(WTFMove(request), results); } static bool isAutomaticTextReplacementType(TextCheckingType type) @@ -2453,24 +2427,25 @@ static bool isAutomaticTextReplacementType(TextCheckingType type) static void correctSpellcheckingPreservingTextCheckingParagraph(TextCheckingParagraph& paragraph, PassRefPtr<Range> rangeToReplace, const String& replacement, int resultLocation, int resultLength) { - ContainerNode* scope = toContainerNode(highestAncestor(paragraph.paragraphRange()->startContainer())); + auto& scope = downcast<ContainerNode>(paragraph.paragraphRange()->startContainer().rootNode()); size_t paragraphLocation; size_t paragraphLength; - TextIterator::getLocationAndLengthFromRange(scope, paragraph.paragraphRange().get(), paragraphLocation, paragraphLength); + TextIterator::getLocationAndLengthFromRange(&scope, paragraph.paragraphRange().get(), paragraphLocation, paragraphLength); applyCommand(SpellingCorrectionCommand::create(rangeToReplace, replacement)); // TextCheckingParagraph may be orphaned after SpellingCorrectionCommand mutated DOM. // See <rdar://10305315>, http://webkit.org/b/89526. - RefPtr<Range> newParagraphRange = TextIterator::rangeFromLocationAndLength(scope, paragraphLocation, paragraphLength + replacement.length() - resultLength); + RefPtr<Range> newParagraphRange = TextIterator::rangeFromLocationAndLength(&scope, paragraphLocation, paragraphLength + replacement.length() - resultLength); paragraph = TextCheckingParagraph(TextIterator::subrange(newParagraphRange.get(), resultLocation, replacement.length()), newParagraphRange); } void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vector<TextCheckingResult>& results) { + Ref<Frame> protection(m_frame); ASSERT(request); TextCheckingTypeMask textCheckingOptions = request->data().mask(); @@ -2479,7 +2454,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect const bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling; const bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar; const bool shouldMarkLink = textCheckingOptions & TextCheckingTypeLink; - const bool shouldPerformReplacement = textCheckingOptions & TextCheckingTypeReplacement; + const bool shouldPerformReplacement = textCheckingOptions & (TextCheckingTypeQuote | TextCheckingTypeDash | TextCheckingTypeReplacement); const bool shouldShowCorrectionPanel = textCheckingOptions & TextCheckingTypeShowCorrectionPanel; const bool shouldCheckForCorrection = shouldShowCorrectionPanel || (textCheckingOptions & TextCheckingTypeCorrection); #if !USE(AUTOCORRECTION_PANEL) @@ -2494,10 +2469,10 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect bool adjustSelectionForParagraphBoundaries = false; if (shouldPerformReplacement || shouldMarkSpelling || shouldCheckForCorrection) { - if (m_frame.selection().selectionType() == VisibleSelection::CaretSelection) { + if (m_frame.selection().selection().selectionType() == VisibleSelection::CaretSelection) { // Attempt to save the caret position so we can restore it later if needed - Position caretPosition = m_frame.selection().end(); - selectionOffset = paragraph.offsetTo(caretPosition, ASSERT_NO_EXCEPTION); + Position caretPosition = m_frame.selection().selection().end(); + selectionOffset = paragraph.offsetTo(caretPosition).releaseReturnValue(); restoreSelectionAfterChange = true; if (selectionOffset > 0 && (selectionOffset > paragraph.textLength() || paragraph.textCharAt(selectionOffset - 1) == newlineCharacter)) adjustSelectionForParagraphBoundaries = true; @@ -2515,7 +2490,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect const int resultLength = results[i].length; const int resultEndLocation = resultLocation + resultLength; const String& replacement = results[i].replacement; - const bool resultEndsAtAmbiguousBoundary = useAmbiguousBoundaryOffset && resultEndLocation == selectionOffset - 1; + const bool resultEndsAtAmbiguousBoundary = useAmbiguousBoundaryOffset && selectionOffset - 1 <= resultEndLocation; // Only mark misspelling if: // 1. Current text checking isn't done for autocorrection, in which case shouldMarkSpelling is false. @@ -2528,16 +2503,14 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect RefPtr<Range> misspellingRange = paragraph.subrange(resultLocation, resultLength); if (!m_alternativeTextController->isSpellingMarkerAllowed(misspellingRange)) continue; - misspellingRange->startContainer()->document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling, replacement); + misspellingRange->startContainer().document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling, replacement); } else if (shouldMarkGrammar && resultType == TextCheckingTypeGrammar && paragraph.checkingRangeCovers(resultLocation, resultLength)) { ASSERT(resultLength > 0 && resultLocation >= 0); - const Vector<GrammarDetail>& details = results[i].details; - for (unsigned j = 0; j < details.size(); j++) { - const GrammarDetail& detail = details[j]; + for (auto& detail : results[i].details) { ASSERT(detail.length > 0 && detail.location >= 0); if (paragraph.checkingRangeCovers(resultLocation + detail.location, detail.length)) { RefPtr<Range> badGrammarRange = paragraph.subrange(resultLocation + detail.location, detail.length); - badGrammarRange->startContainer()->document().markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail.userDescription); + badGrammarRange->startContainer().document().markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail.userDescription); } } } else if (resultEndLocation <= spellingRangeEndOffset && resultEndLocation >= paragraph.checkingStart() @@ -2578,7 +2551,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect continue; } - VisibleSelection selectionToReplace(rangeToReplace.get(), DOWNSTREAM); + VisibleSelection selectionToReplace(*rangeToReplace, DOWNSTREAM); if (selectionToReplace != m_frame.selection().selection()) { if (!m_frame.selection().shouldChangeSelection(selectionToReplace)) continue; @@ -2590,7 +2563,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect restoreSelectionAfterChange = false; if (canEditRichly()) applyCommand(CreateLinkCommand::create(document(), replacement)); - } else if (canEdit() && shouldInsertText(replacement, rangeToReplace.get(), EditorInsertActionTyped)) { + } else if (canEdit() && shouldInsertText(replacement, rangeToReplace.get(), EditorInsertAction::Typed)) { correctSpellcheckingPreservingTextCheckingParagraph(paragraph, rangeToReplace, replacement, resultLocation, resultLength); if (AXObjectCache* cache = document().existingAXObjectCache()) { @@ -2607,9 +2580,13 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect if (resultLocation < selectionOffset) selectionOffset += replacement.length() - resultLength; - // Add a marker so that corrections can easily be undone and won't be re-corrected. - if (resultType == TextCheckingTypeCorrection) - m_alternativeTextController->markCorrection(paragraph.subrange(resultLocation, replacement.length()), replacedString); + if (resultType == TextCheckingTypeCorrection) { + RefPtr<Range> replacementRange = paragraph.subrange(resultLocation, replacement.length()); + m_alternativeTextController->recordAutocorrectionResponse(AutocorrectionResponse::Accepted, replacedString, replacementRange); + + // Add a marker so that corrections can easily be undone and won't be re-corrected. + m_alternativeTextController->markCorrection(WTFMove(replacementRange), replacedString); + } } } } @@ -2625,7 +2602,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity); } else { // If this fails for any reason, the fallback is to go one position beyond the last replacement - m_frame.selection().moveTo(m_frame.selection().end()); + m_frame.selection().moveTo(m_frame.selection().selection().end()); m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity); } } @@ -2640,14 +2617,14 @@ void Editor::changeBackToReplacedString(const String& replacedString) return; RefPtr<Range> selection = selectedRange(); - if (!shouldInsertText(replacedString, selection.get(), EditorInsertActionPasted)) + if (!shouldInsertText(replacedString, selection.get(), EditorInsertAction::Pasted)) return; - m_alternativeTextController->recordAutocorrectionResponseReversed(replacedString, selection); + m_alternativeTextController->recordAutocorrectionResponse(AutocorrectionResponse::Reverted, replacedString, selection); TextCheckingParagraph paragraph(selection); - replaceSelectionWithText(replacedString, false, false); + replaceSelectionWithText(replacedString, false, false, EditActionInsert); RefPtr<Range> changedRange = paragraph.subrange(paragraph.checkingStart(), replacedString.length()); - changedRange->startContainer()->document().markers().addMarker(changedRange.get(), DocumentMarker::Replacement, String()); + changedRange->startContainer().document().markers().addMarker(changedRange.get(), DocumentMarker::Replacement, String()); m_alternativeTextController->markReversed(changedRange.get()); #else ASSERT_NOT_REACHED(); @@ -2749,9 +2726,9 @@ void Editor::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionA // of marker that contains the word in question, and remove marker on that whole range. RefPtr<Range> wordRange = Range::create(document(), startOfFirstWord.deepEquivalent(), endOfLastWord.deepEquivalent()); - Vector<DocumentMarker*> markers = document().markers().markersInRange(wordRange.get(), DocumentMarker::DictationAlternatives); - for (size_t i = 0; i < markers.size(); ++i) - m_alternativeTextController->removeDictationAlternativesForMarker(markers[i]); + Vector<RenderedDocumentMarker*> markers = document().markers().markersInRange(wordRange.get(), DocumentMarker::DictationAlternatives); + for (auto* marker : markers) + m_alternativeTextController->removeDictationAlternativesForMarker(*marker); #if PLATFORM(IOS) document().markers().removeMarkers(wordRange.get(), DocumentMarker::Spelling | DocumentMarker::CorrectionIndicator | DocumentMarker::SpellCheckingExemption | DocumentMarker::DictationAlternatives | DocumentMarker::DictationPhraseWithAlternatives, DocumentMarkerController::RemovePartiallyOverlappingMarker); @@ -2780,7 +2757,7 @@ PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint) IntPoint framePoint = frameView->windowToContents(windowPoint); VisibleSelection selection(frame->visiblePositionForPoint(framePoint)); - return avoidIntersectionWithDeleteButtonController(selection.toNormalizedRange().get()); + return selection.toNormalizedRange(); } void Editor::revealSelectionAfterEditingOperation(const ScrollAlignment& alignment, RevealExtentOption revealExtentOption) @@ -2788,33 +2765,39 @@ void Editor::revealSelectionAfterEditingOperation(const ScrollAlignment& alignme if (m_ignoreCompositionSelectionChange) return; - m_frame.selection().revealSelection(alignment, revealExtentOption); +#if PLATFORM(IOS) + SelectionRevealMode revealMode = SelectionRevealMode::RevealUpToMainFrame; +#else + SelectionRevealMode revealMode = SelectionRevealMode::Reveal; +#endif + + m_frame.selection().revealSelection(revealMode, alignment, revealExtentOption); } -void Editor::setIgnoreCompositionSelectionChange(bool ignore) +void Editor::setIgnoreCompositionSelectionChange(bool ignore, RevealSelection shouldRevealExistingSelection) { if (m_ignoreCompositionSelectionChange == ignore) return; m_ignoreCompositionSelectionChange = ignore; #if PLATFORM(IOS) - // FIXME: Merge this to open source https://bugs.webkit.org/show_bug.cgi?id=38830 + // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830> if (!ignore) - notifyComponentsOnChangedSelection(m_frame.selection().selection(), 0); + respondToChangedSelection(m_frame.selection().selection(), 0); #endif - if (!ignore) + if (!ignore && shouldRevealExistingSelection == RevealSelection::Yes) revealSelectionAfterEditingOperation(ScrollAlignment::alignToEdgeIfNeeded, RevealExtent); } -PassRefPtr<Range> Editor::compositionRange() const +RefPtr<Range> Editor::compositionRange() const { if (!m_compositionNode) - return 0; + return nullptr; unsigned length = m_compositionNode->length(); unsigned start = std::min(m_compositionStart, length); unsigned end = std::min(std::max(start, m_compositionEnd), length); if (start >= end) - return 0; + return nullptr; return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end); } @@ -2822,10 +2805,11 @@ bool Editor::getCompositionSelection(unsigned& selectionStart, unsigned& selecti { if (!m_compositionNode) return false; - Position start = m_frame.selection().start(); + const VisibleSelection& selection = m_frame.selection().selection(); + Position start = selection.start(); if (start.deprecatedNode() != m_compositionNode) return false; - Position end = m_frame.selection().end(); + Position end = selection.end(); if (end.deprecatedNode() != m_compositionNode) return false; @@ -2860,7 +2844,7 @@ void Editor::transpose() RefPtr<Range> range = makeRange(previous, next); if (!range) return; - VisibleSelection newSelection(range.get(), DOWNSTREAM); + VisibleSelection newSelection(*range, DOWNSTREAM); // Transpose the two characters. String text = plainText(range.get()); @@ -2876,22 +2860,34 @@ void Editor::transpose() } // Insert the transposed characters. - if (!shouldInsertText(transposed, range.get(), EditorInsertActionTyped)) + if (!shouldInsertText(transposed, range.get(), EditorInsertAction::Typed)) return; - replaceSelectionWithText(transposed, false, false); + replaceSelectionWithText(transposed, false, false, EditActionInsert); +} + +void Editor::addRangeToKillRing(const Range& range, KillRingInsertionMode mode) +{ + addTextToKillRing(plainText(&range), mode); } -void Editor::addToKillRing(Range* range, bool prepend) +void Editor::addTextToKillRing(const String& text, KillRingInsertionMode mode) { if (m_shouldStartNewKillRingSequence) killRing().startNewSequence(); - String text = plainText(range); - if (prepend) + m_shouldStartNewKillRingSequence = false; + + // If the kill was from a backwards motion, prepend to the kill ring. + // This will ensure that alternating forward and backward kills will + // build up the original string in the kill ring without permuting it. + switch (mode) { + case KillRingInsertionMode::PrependText: killRing().prepend(text); - else + break; + case KillRingInsertionMode::AppendText: killRing().append(text); - m_shouldStartNewKillRingSequence = false; + break; + } } void Editor::startAlternativeTextUITimer() @@ -2910,8 +2906,10 @@ void Editor::dismissCorrectionPanelAsIgnored() m_alternativeTextController->dismiss(ReasonForDismissingAlternativeTextIgnored); } -void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, FrameSelection::SetSelectionOptions options) +void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, FrameSelection::SetSelectionOptions options) { + Ref<Frame> protection(m_frame); + // If the new selection is orphaned, then don't update the selection. if (newSelection.start().isOrphan() || newSelection.end().isOrphan()) return; @@ -2932,7 +2930,7 @@ void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, // does not call EditorClient::respondToChangedSelection(), which, on the Mac, sends selection change notifications and // starts a new kill ring sequence, but we want to do these things (matches AppKit). #if PLATFORM(IOS) - // FIXME: Merge this to open source https://bugs.webkit.org/show_bug.cgi?id=38830 + // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830> if (m_ignoreCompositionSelectionChange) return; #endif @@ -2945,11 +2943,9 @@ String Editor::selectedText() const return selectedText(TextIteratorDefaultBehavior); } -String Editor::selectedTextForClipboard() const +String Editor::selectedTextForDataTransfer() const { - if (m_frame.settings().selectionIncludesAltImageText()) - return selectedText(TextIteratorEmitsImageAltText); - return selectedText(); + return selectedText(TextIteratorEmitsImageAltText); } String Editor::selectedText(TextIteratorBehavior behavior) const @@ -2969,12 +2965,9 @@ static inline void collapseCaretWidth(IntRect& rect) IntRect Editor::firstRectForRange(Range* range) const { - ASSERT(range->startContainer()); - ASSERT(range->endContainer()); - VisiblePosition startVisiblePosition(range->startPosition(), DOWNSTREAM); - if (range->collapsed(ASSERT_NO_EXCEPTION)) { + if (range->collapsed()) { // FIXME: Getting caret rect and removing caret width is a very roundabout way to get collapsed range location. // In particular, width adjustment doesn't work for rotated text. IntRect startCaretRect = RenderedPosition(startVisiblePosition).absoluteRect(); @@ -3014,22 +3007,20 @@ bool Editor::shouldChangeSelection(const VisibleSelection& oldSelection, const V return client() && client()->shouldChangeSelectedRange(oldSelection.toNormalizedRange().get(), newSelection.toNormalizedRange().get(), affinity, stillSelecting); } -void Editor::computeAndSetTypingStyle(StyleProperties* style, EditAction editingAction) +void Editor::computeAndSetTypingStyle(EditingStyle& style, EditAction editingAction) { - if (!style || style->isEmpty()) { + if (style.isEmpty()) { m_frame.selection().clearTypingStyle(); return; } // Calculate the current typing style. RefPtr<EditingStyle> typingStyle; - if (m_frame.selection().typingStyle()) { - typingStyle = m_frame.selection().typingStyle()->copy(); - typingStyle->overrideWithStyle(style); - } else - typingStyle = EditingStyle::create(style); - - typingStyle->prepareToApplyAt(m_frame.selection().selection().visibleStart().deepEquivalent(), EditingStyle::PreserveWritingDirection); + if (auto existingTypingStyle = m_frame.selection().typingStyle()) + typingStyle = existingTypingStyle->copy(); + else + typingStyle = EditingStyle::create(); + typingStyle->overrideTypingStyleAt(style, m_frame.selection().selection().visibleStart().deepEquivalent()); // Handle block styles, substracting these from the typing style. RefPtr<EditingStyle> blockStyle = typingStyle->extractAndRemoveBlockProperties(); @@ -3040,6 +3031,11 @@ void Editor::computeAndSetTypingStyle(StyleProperties* style, EditAction editing m_frame.selection().setTypingStyle(typingStyle); } +void Editor::computeAndSetTypingStyle(StyleProperties& properties, EditAction editingAction) +{ + return computeAndSetTypingStyle(EditingStyle::create(&properties), editingAction); +} + void Editor::textFieldDidBeginEditing(Element* e) { if (client()) @@ -3081,29 +3077,29 @@ void Editor::textDidChangeInTextArea(Element* e) void Editor::applyEditingStyleToBodyElement() const { - RefPtr<NodeList> list = document().getElementsByTagName("body"); - unsigned len = list->length(); - for (unsigned i = 0; i < len; i++) - applyEditingStyleToElement(toElement(list->item(i))); + auto collection = document().getElementsByTagName(HTMLNames::bodyTag.localName()); + unsigned length = collection->length(); + for (unsigned i = 0; i < length; ++i) + applyEditingStyleToElement(collection->item(i)); } void Editor::applyEditingStyleToElement(Element* element) const { - if (!element) - return; - ASSERT(element->isStyledElement()); - if (!element->isStyledElement()) + ASSERT(!element || is<StyledElement>(*element)); + if (!is<StyledElement>(element)) return; // Mutate using the CSSOM wrapper so we get the same event behavior as a script. - CSSStyleDeclaration* style = toStyledElement(element)->style(); - style->setPropertyInternal(CSSPropertyWordWrap, "break-word", false, IGNORE_EXCEPTION); - style->setPropertyInternal(CSSPropertyWebkitNbspMode, "space", false, IGNORE_EXCEPTION); - style->setPropertyInternal(CSSPropertyWebkitLineBreak, "after-white-space", false, IGNORE_EXCEPTION); + CSSStyleDeclaration* style = downcast<StyledElement>(*element).cssomStyle(); + style->setPropertyInternal(CSSPropertyWordWrap, "break-word", false); + style->setPropertyInternal(CSSPropertyWebkitNbspMode, "space", false); + style->setPropertyInternal(CSSPropertyWebkitLineBreak, "after-white-space", false); } bool Editor::findString(const String& target, FindOptions options) { + Ref<Frame> protection(m_frame); + VisibleSelection selection = m_frame.selection().selection(); RefPtr<Range> resultRange = rangeOfString(target, selection.firstRange().get(), options); @@ -3111,27 +3107,18 @@ bool Editor::findString(const String& target, FindOptions options) if (!resultRange) return false; - m_frame.selection().setSelection(VisibleSelection(resultRange.get(), DOWNSTREAM)); - m_frame.selection().revealSelection(); - return true; -} - -PassRefPtr<Range> Editor::findStringAndScrollToVisible(const String& target, Range* previousMatch, FindOptions options) -{ - RefPtr<Range> nextMatch = rangeOfString(target, previousMatch, options); - if (!nextMatch) - return 0; + m_frame.selection().setSelection(VisibleSelection(*resultRange, DOWNSTREAM)); - nextMatch->firstNode()->renderer()->scrollRectToVisible(nextMatch->boundingBox(), - ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded); + if (!(options & DoNotRevealSelection)) + m_frame.selection().revealSelection(); - return nextMatch.release(); + return true; } -PassRefPtr<Range> Editor::rangeOfString(const String& target, Range* referenceRange, FindOptions options) +RefPtr<Range> Editor::rangeOfString(const String& target, Range* referenceRange, FindOptions options) { if (target.isEmpty()) - return 0; + return nullptr; // Start from an edge of the reference range, if there's a reference range that's not in shadow content. Which edge // is used depends on whether we're searching forward or backward, and whether startInSelection is set. @@ -3146,19 +3133,19 @@ PassRefPtr<Range> Editor::rangeOfString(const String& target, Range* referenceRa searchRange->setEnd(startInReferenceRange ? referenceRange->endPosition() : referenceRange->startPosition()); } - RefPtr<Node> shadowTreeRoot = referenceRange && referenceRange->startContainer() ? referenceRange->startContainer()->nonBoundaryShadowTreeRootNode() : 0; + RefPtr<ShadowRoot> shadowTreeRoot = referenceRange ? referenceRange->startContainer().containingShadowRoot() : nullptr; if (shadowTreeRoot) { if (forward) - searchRange->setEnd(shadowTreeRoot.get(), shadowTreeRoot->childNodeCount()); + searchRange->setEnd(*shadowTreeRoot, shadowTreeRoot->countChildNodes()); else - searchRange->setStart(shadowTreeRoot.get(), 0); + searchRange->setStart(*shadowTreeRoot, 0); } - RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, options)); + RefPtr<Range> resultRange = findPlainText(*searchRange, target, options); // If we started in the reference range and the found range exactly matches the reference range, find again. // Build a selection with the found range to remove collapsed whitespace. // Compare ranges instead of selection objects to ignore the way that the current selection was made. - if (startInReferenceRange && areRangesEqual(VisibleSelection(resultRange.get()).toNormalizedRange().get(), referenceRange)) { + if (startInReferenceRange && areRangesEqual(VisibleSelection(*resultRange).toNormalizedRange().get(), referenceRange)) { searchRange = rangeOfContents(document()); if (forward) searchRange->setStart(referenceRange->endPosition()); @@ -3167,48 +3154,49 @@ PassRefPtr<Range> Editor::rangeOfString(const String& target, Range* referenceRa if (shadowTreeRoot) { if (forward) - searchRange->setEnd(shadowTreeRoot.get(), shadowTreeRoot->childNodeCount()); + searchRange->setEnd(*shadowTreeRoot, shadowTreeRoot->countChildNodes()); else - searchRange->setStart(shadowTreeRoot.get(), 0); + searchRange->setStart(*shadowTreeRoot, 0); } - resultRange = findPlainText(searchRange.get(), target, options); + resultRange = findPlainText(*searchRange, target, options); } // If nothing was found in the shadow tree, search in main content following the shadow tree. - if (resultRange->collapsed(ASSERT_NO_EXCEPTION) && shadowTreeRoot) { + if (resultRange->collapsed() && shadowTreeRoot) { searchRange = rangeOfContents(document()); - if (forward) - searchRange->setStartAfter(shadowTreeRoot->shadowHost()); - else - searchRange->setEndBefore(shadowTreeRoot->shadowHost()); + if (shadowTreeRoot->shadowHost()) { + if (forward) + searchRange->setStartAfter(*shadowTreeRoot->shadowHost()); + else + searchRange->setEndBefore(*shadowTreeRoot->shadowHost()); + } - resultRange = findPlainText(searchRange.get(), target, options); + resultRange = findPlainText(*searchRange, target, options); } // If we didn't find anything and we're wrapping, search again in the entire document (this will // redundantly re-search the area already searched in some cases). - if (resultRange->collapsed(ASSERT_NO_EXCEPTION) && options & WrapAround) { + if (resultRange->collapsed() && options & WrapAround) { searchRange = rangeOfContents(document()); - resultRange = findPlainText(searchRange.get(), target, options); + resultRange = findPlainText(*searchRange, target, options); // We used to return false here if we ended up with the same range that we started with // (e.g., the reference range was already the only instance of this text). But we decided that // this should be a success case instead, so we'll just fall through in that case. } - return resultRange->collapsed(ASSERT_NO_EXCEPTION) ? 0 : resultRange.release(); + return resultRange->collapsed() ? nullptr : resultRange; } -static bool isFrameInRange(Frame* frame, Range* range) +static bool isFrameInRange(Frame& frame, Range& range) { - bool inRange = false; - for (HTMLFrameOwnerElement* ownerElement = frame->ownerElement(); ownerElement; ownerElement = ownerElement->document().ownerElement()) { - if (&ownerElement->document() == &range->ownerDocument()) { - inRange = range->intersectsNode(ownerElement, IGNORE_EXCEPTION); - break; + for (auto* ownerElement = frame.ownerElement(); ownerElement; ownerElement = ownerElement->document().ownerElement()) { + if (&ownerElement->document() == &range.ownerDocument()) { + auto result = range.intersectsNode(*ownerElement); + return !result.hasException() && result.releaseReturnValue(); } } - return inRange; + return false; } unsigned Editor::countMatchesForText(const String& target, Range* range, FindOptions options, unsigned limit, bool markMatches, Vector<RefPtr<Range>>* matches) @@ -3220,24 +3208,24 @@ unsigned Editor::countMatchesForText(const String& target, Range* range, FindOpt if (range) { if (&range->ownerDocument() == &document()) searchRange = range; - else if (!isFrameInRange(&m_frame, range)) + else if (!isFrameInRange(m_frame, *range)) return 0; } if (!searchRange) searchRange = rangeOfContents(document()); - Node* originalEndContainer = searchRange->endContainer(); + Node& originalEndContainer = searchRange->endContainer(); int originalEndOffset = searchRange->endOffset(); unsigned matchCount = 0; do { - RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, options & ~Backwards)); - if (resultRange->collapsed(IGNORE_EXCEPTION)) { - if (!resultRange->startContainer()->isInShadowTree()) + RefPtr<Range> resultRange(findPlainText(*searchRange, target, options & ~Backwards)); + if (resultRange->collapsed()) { + if (!resultRange->startContainer().isInShadowTree()) break; - searchRange->setStartAfter(resultRange->startContainer()->shadowHost(), IGNORE_EXCEPTION); - searchRange->setEnd(originalEndContainer, originalEndOffset, IGNORE_EXCEPTION); + searchRange->setStartAfter(*resultRange->startContainer().shadowHost()); + searchRange->setEnd(originalEndContainer, originalEndOffset); continue; } @@ -3256,32 +3244,13 @@ unsigned Editor::countMatchesForText(const String& target, Range* range, FindOpt // result range. There is no need to use a VisiblePosition here, // since findPlainText will use a TextIterator to go over the visible // text nodes. - searchRange->setStart(resultRange->endContainer(IGNORE_EXCEPTION), resultRange->endOffset(IGNORE_EXCEPTION), IGNORE_EXCEPTION); + searchRange->setStart(resultRange->endContainer(), resultRange->endOffset()); Node* shadowTreeRoot = searchRange->shadowRoot(); - if (searchRange->collapsed(IGNORE_EXCEPTION) && shadowTreeRoot) - searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount(), IGNORE_EXCEPTION); + if (searchRange->collapsed() && shadowTreeRoot) + searchRange->setEnd(*shadowTreeRoot, shadowTreeRoot->countChildNodes()); } while (true); - if (markMatches || matches) { - // Do a "fake" paint in order to execute the code that computes the rendered rect for each text match. - if (m_frame.view() && m_frame.contentRenderer()) { - document().updateLayout(); // Ensure layout is up to date. - // FIXME: unclear if we need LegacyIOSDocumentVisibleRect. - // FIXME: this should probably look at paintsEntireContents() - LayoutRect visibleRect = m_frame.view()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); - if (!visibleRect.isEmpty()) { - GraphicsContext context((PlatformGraphicsContext*)0); - context.setPaintingDisabled(true); - - PaintBehavior oldBehavior = m_frame.view()->paintBehavior(); - m_frame.view()->setPaintBehavior(oldBehavior | PaintBehaviorFlattenCompositingLayers); - m_frame.view()->paintContents(&context, enclosingIntRect(visibleRect)); - m_frame.view()->setPaintBehavior(oldBehavior); - } - } - } - return matchCount; } @@ -3294,11 +3263,149 @@ void Editor::setMarkedTextMatchesAreHighlighted(bool flag) document().markers().repaintMarkers(DocumentMarker::TextMatch); } -void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options) +#if !PLATFORM(MAC) +void Editor::selectionWillChange() { - m_alternativeTextController->stopPendingCorrection(oldSelection); +} +#endif + +void Editor::respondToChangedSelection(const VisibleSelection&, FrameSelection::SetSelectionOptions options) +{ +#if PLATFORM(IOS) + // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830> + if (m_ignoreCompositionSelectionChange) + return; +#endif + + if (client()) + client()->respondToChangedSelection(&m_frame); + +#if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS) + if (shouldDetectTelephoneNumbers()) + m_telephoneNumberDetectionUpdateTimer.startOneShot(0); +#endif + + setStartNewKillRingSequence(true); - bool closeTyping = options & FrameSelection::CloseTyping; + if (m_editorUIUpdateTimer.isActive()) + return; + + // Don't check spelling and grammar if the change of selection is triggered by spelling correction itself. + m_editorUIUpdateTimerShouldCheckSpellingAndGrammar = options & FrameSelection::CloseTyping + && !(options & FrameSelection::SpellCorrectionTriggered); + m_editorUIUpdateTimerWasTriggeredByDictation = options & FrameSelection::DictationTriggered; + m_editorUIUpdateTimer.startOneShot(0); +} + +#if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS) + +bool Editor::shouldDetectTelephoneNumbers() +{ + if (!m_frame.document()) + return false; + return document().isTelephoneNumberParsingEnabled() && TelephoneNumberDetector::isSupported(); +} + +void Editor::scanSelectionForTelephoneNumbers() +{ + if (!shouldDetectTelephoneNumbers() || !client()) + return; + + m_detectedTelephoneNumberRanges.clear(); + + Vector<RefPtr<Range>> markedRanges; + + FrameSelection& frameSelection = m_frame.selection(); + if (!frameSelection.isRange()) { + m_frame.mainFrame().servicesOverlayController().selectedTelephoneNumberRangesChanged(); + return; + } + RefPtr<Range> selectedRange = frameSelection.toNormalizedRange(); + + // Extend the range a few characters in each direction to detect incompletely selected phone numbers. + static const int charactersToExtend = 15; + const VisibleSelection& visibleSelection = frameSelection.selection(); + Position start = visibleSelection.start(); + Position end = visibleSelection.end(); + for (int i = 0; i < charactersToExtend; ++i) { + start = start.previous(Character); + end = end.next(Character); + } + + FrameSelection extendedSelection; + extendedSelection.setStart(start); + extendedSelection.setEnd(end); + RefPtr<Range> extendedRange = extendedSelection.toNormalizedRange(); + + if (!extendedRange) { + m_frame.mainFrame().servicesOverlayController().selectedTelephoneNumberRangesChanged(); + return; + } + + scanRangeForTelephoneNumbers(*extendedRange, extendedRange->text(), markedRanges); + + // Only consider ranges with a detected telephone number if they overlap with the actual selection range. + for (auto& range : markedRanges) { + if (rangesOverlap(range.get(), selectedRange.get())) + m_detectedTelephoneNumberRanges.append(range); + } + + m_frame.mainFrame().servicesOverlayController().selectedTelephoneNumberRangesChanged(); +} + +void Editor::scanRangeForTelephoneNumbers(Range& range, const StringView& stringView, Vector<RefPtr<Range>>& markedRanges) +{ + // Don't scan for phone numbers inside editable regions. + Node& startNode = range.startContainer(); + if (startNode.hasEditableStyle()) + return; + + // relativeStartPosition and relativeEndPosition are the endpoints of the phone number range, + // relative to the scannerPosition + unsigned length = stringView.length(); + unsigned scannerPosition = 0; + int relativeStartPosition = 0; + int relativeEndPosition = 0; + + auto characters = stringView.upconvertedCharacters(); + + while (scannerPosition < length && TelephoneNumberDetector::find(&characters[scannerPosition], length - scannerPosition, &relativeStartPosition, &relativeEndPosition)) { + // The convention in the Data Detectors framework is that the end position is the first character NOT in the phone number + // (that is, the length of the range is relativeEndPosition - relativeStartPosition). So subtract 1 to get the same + // convention as the old WebCore phone number parser (so that the rest of the code is still valid if we want to go back + // to the old parser). + --relativeEndPosition; + + ASSERT(scannerPosition + relativeEndPosition < length); + + unsigned subrangeOffset = scannerPosition + relativeStartPosition; + unsigned subrangeLength = relativeEndPosition - relativeStartPosition + 1; + + RefPtr<Range> subrange = TextIterator::subrange(&range, subrangeOffset, subrangeLength); + + markedRanges.append(subrange); + range.ownerDocument().markers().addMarker(subrange.get(), DocumentMarker::TelephoneNumber); + + scannerPosition += relativeEndPosition + 1; + } +} + +#endif // ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS) + +void Editor::updateEditorUINowIfScheduled() +{ + if (!m_editorUIUpdateTimer.isActive()) + return; + m_editorUIUpdateTimer.stop(); + editorUIUpdateTimerFired(); +} + +void Editor::editorUIUpdateTimerFired() +{ + VisibleSelection oldSelection = m_oldSelectionForEditorUIUpdate; + + m_alternativeTextController->stopPendingCorrection(oldSelection); + bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabled(); bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && isGrammarCheckingEnabled(); if (isContinuousSpellCheckingEnabled) { @@ -3325,13 +3432,10 @@ void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, Fra newSelectedSentence = VisibleSelection(startOfSentence(newStart), endOfSentence(newStart)); } - // Don't check spelling and grammar if the change of selection is triggered by spelling correction itself. - bool shouldCheckSpellingAndGrammar = !(options & FrameSelection::SpellCorrectionTriggered); - // When typing we check spelling elsewhere, so don't redo it here. // If this is a change in selection resulting from a delete operation, // oldSelection may no longer be in the document. - if (shouldCheckSpellingAndGrammar && closeTyping && oldSelection.isContentEditable() && oldSelection.start().deprecatedNode() && oldSelection.start().anchorNode()->inDocument()) { + if (m_editorUIUpdateTimerShouldCheckSpellingAndGrammar && oldSelection.isContentEditable() && oldSelection.start().deprecatedNode() && oldSelection.start().anchorNode()->isConnected()) { VisiblePosition oldStart(oldSelection.visibleStart()); VisibleSelection oldAdjacentWords = VisibleSelection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary)); if (oldAdjacentWords != newAdjacentWords) { @@ -3359,38 +3463,40 @@ void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, Fra if (!isContinuousGrammarCheckingEnabled) document().markers().removeMarkers(DocumentMarker::Grammar); - notifyComponentsOnChangedSelection(oldSelection, options); + if (!m_editorUIUpdateTimerWasTriggeredByDictation) + m_alternativeTextController->respondToChangedSelection(oldSelection); + + m_oldSelectionForEditorUIUpdate = m_frame.selection().selection(); } static Node* findFirstMarkable(Node* node) { while (node) { if (!node->renderer()) - return 0; + return nullptr; if (node->renderer()->isTextOrLineBreak()) return node; - if (isHTMLTextFormControlElement(*node)) - node = toHTMLTextFormControlElement(node)->visiblePositionForIndex(1).deepEquivalent().deprecatedNode(); + if (is<HTMLTextFormControlElement>(*node)) + node = downcast<HTMLTextFormControlElement>(*node).visiblePositionForIndex(1).deepEquivalent().deprecatedNode(); else if (node->firstChild()) node = node->firstChild(); else node = node->nextSibling(); } - return 0; + return nullptr; } bool Editor::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType, int from, int length) const { - Node* node = findFirstMarkable(m_frame.selection().start().deprecatedNode()); + Node* node = findFirstMarkable(m_frame.selection().selection().start().deprecatedNode()); if (!node) return false; unsigned int startOffset = static_cast<unsigned int>(from); unsigned int endOffset = static_cast<unsigned int>(from + length); - Vector<DocumentMarker*> markers = document().markers().markersFor(node); - for (size_t i = 0; i < markers.size(); ++i) { - DocumentMarker* marker = markers[i]; + Vector<RenderedDocumentMarker*> markers = document().markers().markersFor(node); + for (auto* marker : markers) { if (marker->startOffset() <= startOffset && endOffset <= marker->endOffset() && marker->type() == markerType) return true; } @@ -3398,8 +3504,18 @@ bool Editor::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType, i return false; } -TextCheckingTypeMask Editor::resolveTextCheckingTypeMask(TextCheckingTypeMask textCheckingOptions) +TextCheckingTypeMask Editor::resolveTextCheckingTypeMask(const Node& rootEditableElement, TextCheckingTypeMask textCheckingOptions) { +#if USE(AUTOMATIC_TEXT_REPLACEMENT) && !PLATFORM(IOS) + bool onlyAllowsTextReplacement = false; + if (auto* host = rootEditableElement.shadowHost()) + onlyAllowsTextReplacement = is<HTMLInputElement>(host) && downcast<HTMLInputElement>(*host).isSpellcheckDisabledExceptTextReplacement(); + if (onlyAllowsTextReplacement) + textCheckingOptions &= TextCheckingTypeReplacement; +#else + UNUSED_PARAM(rootEditableElement); +#endif + bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling; bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar; #if !PLATFORM(IOS) @@ -3421,16 +3537,18 @@ TextCheckingTypeMask Editor::resolveTextCheckingTypeMask(TextCheckingTypeMask te #if USE(AUTOMATIC_TEXT_REPLACEMENT) bool shouldPerformReplacement = textCheckingOptions & TextCheckingTypeReplacement; if (shouldPerformReplacement) { - if (isAutomaticLinkDetectionEnabled()) - checkingTypes |= TextCheckingTypeLink; - if (isAutomaticQuoteSubstitutionEnabled()) - checkingTypes |= TextCheckingTypeQuote; - if (isAutomaticDashSubstitutionEnabled()) - checkingTypes |= TextCheckingTypeDash; + if (!onlyAllowsTextReplacement) { + if (isAutomaticLinkDetectionEnabled()) + checkingTypes |= TextCheckingTypeLink; + if (isAutomaticQuoteSubstitutionEnabled()) + checkingTypes |= TextCheckingTypeQuote; + if (isAutomaticDashSubstitutionEnabled()) + checkingTypes |= TextCheckingTypeDash; + if (shouldMarkSpelling && isAutomaticSpellingCorrectionEnabled()) + checkingTypes |= TextCheckingTypeCorrection; + } if (isAutomaticTextReplacementEnabled()) checkingTypes |= TextCheckingTypeReplacement; - if (shouldMarkSpelling && isAutomaticSpellingCorrectionEnabled()) - checkingTypes |= TextCheckingTypeCorrection; } #endif #endif // !PLATFORM(IOS) @@ -3438,11 +3556,68 @@ TextCheckingTypeMask Editor::resolveTextCheckingTypeMask(TextCheckingTypeMask te return checkingTypes; } -void Editor::deviceScaleFactorChanged() +static RefPtr<Range> candidateRangeForSelection(Frame& frame) { -#if ENABLE(DELETION_UI) - m_deleteButtonController->deviceScaleFactorChanged(); -#endif + const VisibleSelection& selection = frame.selection().selection(); + return selection.isCaret() ? wordRangeFromPosition(selection.start()) : frame.selection().toNormalizedRange(); +} + +static bool candidateWouldReplaceText(const VisibleSelection& selection) +{ + // If the character behind the caret in the current selection is anything but a space or a newline then we should + // replace the whole current word with the candidate. + UChar32 characterAfterSelection, characterBeforeSelection, twoCharacterBeforeSelection = 0; + charactersAroundPosition(selection.visibleStart(), characterAfterSelection, characterBeforeSelection, twoCharacterBeforeSelection); + return !(characterBeforeSelection == '\0' || characterBeforeSelection == '\n' || characterBeforeSelection == ' '); +} + +String Editor::stringForCandidateRequest() const +{ + const VisibleSelection& selection = m_frame.selection().selection(); + RefPtr<Range> rangeForCurrentlyTypedString = candidateRangeForSelection(m_frame); + if (rangeForCurrentlyTypedString && candidateWouldReplaceText(selection)) + return plainText(rangeForCurrentlyTypedString.get()); + + return String(); +} + +RefPtr<Range> Editor::contextRangeForCandidateRequest() const +{ + const VisibleSelection& selection = m_frame.selection().selection(); + return makeRange(startOfParagraph(selection.visibleStart()), endOfParagraph(selection.visibleEnd())); +} + +RefPtr<Range> Editor::rangeForTextCheckingResult(const TextCheckingResult& result) const +{ + if (!result.length) + return nullptr; + + RefPtr<Range> contextRange = contextRangeForCandidateRequest(); + if (!contextRange) + return nullptr; + + return TextIterator::subrange(contextRange.get(), result.location, result.length); +} + +void Editor::handleAcceptedCandidate(TextCheckingResult acceptedCandidate) +{ + const VisibleSelection& selection = m_frame.selection().selection(); + + m_isHandlingAcceptedCandidate = true; + + if (auto range = rangeForTextCheckingResult(acceptedCandidate)) { + if (shouldInsertText(acceptedCandidate.replacement, range.get(), EditorInsertAction::Typed)) { + Ref<ReplaceRangeWithTextCommand> replaceCommand = ReplaceRangeWithTextCommand::create(range.get(), acceptedCandidate.replacement); + applyCommand(replaceCommand.ptr()); + } + } else + insertText(acceptedCandidate.replacement, nullptr); + + RefPtr<Range> insertedCandidateRange = rangeExpandedByCharactersInDirectionAtWordBoundary(selection.visibleStart(), acceptedCandidate.replacement.length(), DirectionBackward); + if (insertedCandidateRange) + insertedCandidateRange->startContainer().document().markers().addMarker(insertedCandidateRange.get(), DocumentMarker::AcceptedCandidate, acceptedCandidate.replacement); + + m_isHandlingAcceptedCandidate = false; } bool Editor::unifiedTextCheckerEnabled() const @@ -3450,7 +3625,7 @@ bool Editor::unifiedTextCheckerEnabled() const return WebCore::unifiedTextCheckerEnabled(&m_frame); } -Vector<String> Editor::dictationAlternativesForMarker(const DocumentMarker* marker) +Vector<String> Editor::dictationAlternativesForMarker(const DocumentMarker& marker) { return m_alternativeTextController->dictationAlternativesForMarker(marker); } @@ -3472,4 +3647,108 @@ Document& Editor::document() const return *m_frame.document(); } +RefPtr<Range> Editor::adjustedSelectionRange() +{ + // FIXME: Why do we need to adjust the selection to include the anchor tag it's in? + // Whoever wrote this code originally forgot to leave us a comment explaining the rationale. + RefPtr<Range> range = selectedRange(); + Node* commonAncestor = range->commonAncestorContainer(); + ASSERT(commonAncestor); + auto* enclosingAnchor = enclosingElementWithTag(firstPositionInNode(commonAncestor), HTMLNames::aTag); + if (enclosingAnchor && comparePositions(firstPositionInOrBeforeNode(range->startPosition().anchorNode()), range->startPosition()) >= 0) + range->setStart(*enclosingAnchor, 0); + return range; +} + +// FIXME: This figures out the current style by inserting a <span>! +const RenderStyle* Editor::styleForSelectionStart(Frame* frame, Node*& nodeToRemove) +{ + nodeToRemove = nullptr; + + if (frame->selection().isNone()) + return nullptr; + + Position position = adjustedSelectionStartForStyleComputation(frame->selection().selection()); + if (!position.isCandidate() || position.isNull()) + return nullptr; + + RefPtr<EditingStyle> typingStyle = frame->selection().typingStyle(); + if (!typingStyle || !typingStyle->style()) + return &position.deprecatedNode()->renderer()->style(); + + auto styleElement = HTMLSpanElement::create(*frame->document()); + + String styleText = typingStyle->style()->asText() + " display: inline"; + styleElement->setAttribute(HTMLNames::styleAttr, styleText); + + styleElement->appendChild(frame->document()->createEditingTextNode(emptyString())); + + auto positionNode = position.deprecatedNode(); + if (!positionNode || !positionNode->parentNode() || positionNode->parentNode()->appendChild(styleElement).hasException()) + return nullptr; + + nodeToRemove = styleElement.ptr(); + + frame->document()->updateStyleIfNeeded(); + return styleElement->renderer() ? &styleElement->renderer()->style() : nullptr; +} + +const Font* Editor::fontForSelection(bool& hasMultipleFonts) const +{ + hasMultipleFonts = false; + + if (!m_frame.selection().isRange()) { + Node* nodeToRemove; + auto* style = styleForSelectionStart(&m_frame, nodeToRemove); // sets nodeToRemove + + const Font* font = nullptr; + if (style) { + font = &style->fontCascade().primaryFont(); + if (nodeToRemove) + nodeToRemove->remove(); + } + + return font; + } + + RefPtr<Range> range = m_frame.selection().toNormalizedRange(); + if (!range) + return nullptr; + + Node* startNode = adjustedSelectionStartForStyleComputation(m_frame.selection().selection()).deprecatedNode(); + if (!startNode) + return nullptr; + + const Font* font = nullptr; + Node* pastEnd = range->pastLastNode(); + // In the loop below, node should eventually match pastEnd and not become null, but we've seen at least one + // unreproducible case where this didn't happen, so check for null also. + for (Node* node = startNode; node && node != pastEnd; node = NodeTraversal::next(*node)) { + auto renderer = node->renderer(); + if (!renderer) + continue; + // FIXME: Are there any node types that have renderers, but that we should be skipping? + const Font& primaryFont = renderer->style().fontCascade().primaryFont(); + if (!font) + font = &primaryFont; + else if (font != &primaryFont) { + hasMultipleFonts = true; + break; + } + } + + return font; +} + +Ref<DocumentFragment> Editor::createFragmentForImageAndURL(const String& url) +{ + auto imageElement = HTMLImageElement::create(*m_frame.document()); + imageElement->setAttributeWithoutSynchronization(HTMLNames::srcAttr, url); + + auto fragment = document().createDocumentFragment(); + fragment->appendChild(imageElement); + + return fragment; +} + } // namespace WebCore diff --git a/Source/WebCore/editing/Editor.h b/Source/WebCore/editing/Editor.h index 90396187c..f9b2ce401 100644 --- a/Source/WebCore/editing/Editor.h +++ b/Source/WebCore/editing/Editor.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006, 2007, 2008, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2006, 2007, 2008, 2013, 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,11 +23,10 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef Editor_h -#define Editor_h +#pragma once -#include "ClipboardAccessPolicy.h" #include "Color.h" +#include "DataTransferAccessPolicy.h" #include "DictationAlternative.h" #include "DocumentMarker.h" #include "EditAction.h" @@ -37,20 +36,23 @@ #include "FindOptions.h" #include "FrameSelection.h" #include "TextChecking.h" -#include "TextIterator.h" +#include "TextEventInputType.h" +#include "TextIteratorBehavior.h" #include "VisibleSelection.h" #include "WritingDirection.h" +#include <memory> -#if PLATFORM(MAC) +#if PLATFORM(COCOA) OBJC_CLASS NSAttributedString; OBJC_CLASS NSDictionary; +OBJC_CLASS NSMutableDictionary; #endif namespace WebCore { class AlternativeTextController; class ArchiveResource; -class Clipboard; +class DataTransfer; class CompositeEditCommand; class DeleteButtonController; class EditCommand; @@ -60,12 +62,14 @@ class EditorInternalCommand; class Frame; class HTMLElement; class HitTestResult; +class KeyboardEvent; class KillRing; class Pasteboard; class SharedBuffer; -class SimpleFontData; +class Font; class SpellCheckRequest; class SpellChecker; +class StaticRange; class StyleProperties; class Text; class TextCheckerClient; @@ -89,160 +93,185 @@ struct CompositionUnderline { enum EditorCommandSource { CommandFromMenuOrKeyBinding, CommandFromDOM, CommandFromDOMWithUserInterface }; enum EditorParagraphSeparator { EditorParagraphSeparatorIsDiv, EditorParagraphSeparatorIsP }; +enum class MailBlockquoteHandling { + RespectBlockquote, + IgnoreBlockquote, +}; + +#if PLATFORM(COCOA) + +struct FragmentAndResources { + RefPtr<DocumentFragment> fragment; + Vector<Ref<ArchiveResource>> resources; +}; + +#endif + class Editor { + WTF_MAKE_FAST_ALLOCATED; public: - static PassOwnPtr<Editor> create(Frame& frame) { return adoptPtr(new Editor(frame)); } + explicit Editor(Frame&); ~Editor(); - EditorClient* client() const; - TextCheckerClient* textChecker() const; + WEBCORE_EXPORT EditorClient* client() const; + WEBCORE_EXPORT TextCheckerClient* textChecker() const; CompositeEditCommand* lastEditCommand() { return m_lastEditCommand.get(); } - void handleKeyboardEvent(KeyboardEvent*); - void handleInputMethodKeydown(KeyboardEvent*); - bool handleTextEvent(TextEvent*); + void handleKeyboardEvent(KeyboardEvent&); + void handleInputMethodKeydown(KeyboardEvent&); + bool handleTextEvent(TextEvent&); - bool canEdit() const; - bool canEditRichly() const; + WEBCORE_EXPORT bool canEdit() const; + WEBCORE_EXPORT bool canEditRichly() const; bool canDHTMLCut(); bool canDHTMLCopy(); - bool canDHTMLPaste(); + WEBCORE_EXPORT bool canDHTMLPaste(); bool tryDHTMLCopy(); bool tryDHTMLCut(); - bool tryDHTMLPaste(); + WEBCORE_EXPORT bool tryDHTMLPaste(); - bool canCut() const; - bool canCopy() const; - bool canPaste() const; - bool canDelete() const; + WEBCORE_EXPORT bool canCut() const; + WEBCORE_EXPORT bool canCopy() const; + WEBCORE_EXPORT bool canPaste() const; + WEBCORE_EXPORT bool canDelete() const; bool canSmartCopyOrDelete(); - void cut(); - void copy(); - void paste(); + WEBCORE_EXPORT void cut(); + WEBCORE_EXPORT void copy(); + WEBCORE_EXPORT void paste(); void paste(Pasteboard&); - void pasteAsPlainText(); - void performDelete(); + WEBCORE_EXPORT void pasteAsPlainText(); + WEBCORE_EXPORT void performDelete(); - void copyURL(const URL&, const String& title); + WEBCORE_EXPORT void copyURL(const URL&, const String& title); void copyURL(const URL&, const String& title, Pasteboard&); #if !PLATFORM(IOS) - void copyImage(const HitTestResult&); + WEBCORE_EXPORT void copyImage(const HitTestResult&); #endif String readPlainTextFromPasteboard(Pasteboard&); - void indent(); - void outdent(); + WEBCORE_EXPORT void indent(); + WEBCORE_EXPORT void outdent(); void transpose(); bool shouldInsertFragment(PassRefPtr<DocumentFragment>, PassRefPtr<Range>, EditorInsertAction); bool shouldInsertText(const String&, Range*, EditorInsertAction) const; - bool shouldDeleteRange(Range*) const; + WEBCORE_EXPORT bool shouldDeleteRange(Range*) const; bool shouldApplyStyle(StyleProperties*, Range*); void respondToChangedContents(const VisibleSelection& endingSelection); bool selectionStartHasStyle(CSSPropertyID, const String& value) const; - TriState selectionHasStyle(CSSPropertyID, const String& value) const; + WEBCORE_EXPORT TriState selectionHasStyle(CSSPropertyID, const String& value) const; String selectionStartCSSPropertyValue(CSSPropertyID); TriState selectionUnorderedListState() const; TriState selectionOrderedListState() const; - PassRefPtr<Node> insertOrderedList(); - PassRefPtr<Node> insertUnorderedList(); - bool canIncreaseSelectionListLevel(); - bool canDecreaseSelectionListLevel(); - PassRefPtr<Node> increaseSelectionListLevel(); - PassRefPtr<Node> increaseSelectionListLevelOrdered(); - PassRefPtr<Node> increaseSelectionListLevelUnordered(); - void decreaseSelectionListLevel(); + WEBCORE_EXPORT PassRefPtr<Node> insertOrderedList(); + WEBCORE_EXPORT PassRefPtr<Node> insertUnorderedList(); + WEBCORE_EXPORT bool canIncreaseSelectionListLevel(); + WEBCORE_EXPORT bool canDecreaseSelectionListLevel(); + WEBCORE_EXPORT RefPtr<Node> increaseSelectionListLevel(); + WEBCORE_EXPORT RefPtr<Node> increaseSelectionListLevelOrdered(); + WEBCORE_EXPORT RefPtr<Node> increaseSelectionListLevelUnordered(); + WEBCORE_EXPORT void decreaseSelectionListLevel(); void removeFormattingAndStyle(); void clearLastEditCommand(); #if PLATFORM(IOS) - void ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping(); + WEBCORE_EXPORT void ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping(); #endif - bool deleteWithDirection(SelectionDirection, TextGranularity, bool killRing, bool isTypingAction); - void deleteSelectionWithSmartDelete(bool smartDelete); -#if PLATFORM(IOS) + WEBCORE_EXPORT bool deleteWithDirection(SelectionDirection, TextGranularity, bool killRing, bool isTypingAction); + WEBCORE_EXPORT void deleteSelectionWithSmartDelete(bool smartDelete, EditAction = EditActionDelete); void clearText(); - void removeUnchangeableStyles(); +#if PLATFORM(IOS) + WEBCORE_EXPORT void removeUnchangeableStyles(); #endif - bool dispatchCPPEvent(const AtomicString&, ClipboardAccessPolicy); + bool dispatchCPPEvent(const AtomicString&, DataTransferAccessPolicy); - void applyStyle(StyleProperties*, EditAction = EditActionUnspecified); + WEBCORE_EXPORT void applyStyle(StyleProperties*, EditAction = EditActionUnspecified); + void applyStyle(RefPtr<EditingStyle>&&, EditAction); void applyParagraphStyle(StyleProperties*, EditAction = EditActionUnspecified); - void applyStyleToSelection(StyleProperties*, EditAction); + WEBCORE_EXPORT void applyStyleToSelection(StyleProperties*, EditAction); + WEBCORE_EXPORT void applyStyleToSelection(Ref<EditingStyle>&&, EditAction); void applyParagraphStyleToSelection(StyleProperties*, EditAction); + // Returns whether or not we should proceed with editing. + bool willApplyEditing(CompositeEditCommand&, Vector<RefPtr<StaticRange>>&&) const; + bool willUnapplyEditing(const EditCommandComposition&) const; + bool willReapplyEditing(const EditCommandComposition&) const; + void appliedEditing(PassRefPtr<CompositeEditCommand>); - void unappliedEditing(PassRefPtr<EditCommandComposition>); - void reappliedEditing(PassRefPtr<EditCommandComposition>); + void unappliedEditing(EditCommandComposition&); + void reappliedEditing(EditCommandComposition&); void unappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction); + // This is off by default, since most editors want this behavior (originally matched IE but not Firefox). void setShouldStyleWithCSS(bool flag) { m_shouldStyleWithCSS = flag; } bool shouldStyleWithCSS() const { return m_shouldStyleWithCSS; } class Command { public: - Command(); + WEBCORE_EXPORT Command(); Command(const EditorInternalCommand*, EditorCommandSource, PassRefPtr<Frame>); - bool execute(const String& parameter = String(), Event* triggeringEvent = 0) const; - bool execute(Event* triggeringEvent) const; + WEBCORE_EXPORT bool execute(const String& parameter = String(), Event* triggeringEvent = nullptr) const; + WEBCORE_EXPORT bool execute(Event* triggeringEvent) const; - bool isSupported() const; - bool isEnabled(Event* triggeringEvent = 0) const; + WEBCORE_EXPORT bool isSupported() const; + WEBCORE_EXPORT bool isEnabled(Event* triggeringEvent = nullptr) const; - TriState state(Event* triggeringEvent = 0) const; - String value(Event* triggeringEvent = 0) const; + WEBCORE_EXPORT TriState state(Event* triggeringEvent = nullptr) const; + String value(Event* triggeringEvent = nullptr) const; - bool isTextInsertion() const; + WEBCORE_EXPORT bool isTextInsertion() const; + WEBCORE_EXPORT bool allowExecutionWhenDisabled() const; private: - const EditorInternalCommand* m_command; + const EditorInternalCommand* m_command { nullptr }; EditorCommandSource m_source; RefPtr<Frame> m_frame; }; - Command command(const String& commandName); // Command source is CommandFromMenuOrKeyBinding. + WEBCORE_EXPORT Command command(const String& commandName); // Command source is CommandFromMenuOrKeyBinding. Command command(const String& commandName, EditorCommandSource); - static bool commandIsSupportedFromMenuOrKeyBinding(const String& commandName); // Works without a frame. + WEBCORE_EXPORT static bool commandIsSupportedFromMenuOrKeyBinding(const String& commandName); // Works without a frame. - bool insertText(const String&, Event* triggeringEvent); + WEBCORE_EXPORT bool insertText(const String&, Event* triggeringEvent, TextEventInputType = TextEventInputKeyboard); bool insertTextForConfirmedComposition(const String& text); - bool insertDictatedText(const String&, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent); + WEBCORE_EXPORT bool insertDictatedText(const String&, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent); bool insertTextWithoutSendingTextEvent(const String&, bool selectInsertedText, TextEvent* triggeringEvent); bool insertLineBreak(); bool insertParagraphSeparator(); + WEBCORE_EXPORT bool insertParagraphSeparatorInQuotedContent(); - bool isContinuousSpellCheckingEnabled() const; - void toggleContinuousSpellChecking(); + WEBCORE_EXPORT bool isContinuousSpellCheckingEnabled() const; + WEBCORE_EXPORT void toggleContinuousSpellChecking(); bool isGrammarCheckingEnabled(); void toggleGrammarChecking(); void ignoreSpelling(); void learnSpelling(); int spellCheckerDocumentTag(); - bool isSelectionUngrammatical(); + WEBCORE_EXPORT bool isSelectionUngrammatical(); String misspelledSelectionString() const; String misspelledWordAtCaretOrRange(Node* clickedNode) const; Vector<String> guessesForMisspelledWord(const String&) const; Vector<String> guessesForMisspelledOrUngrammatical(bool& misspelled, bool& ungrammatical); bool isSpellCheckingEnabledInFocusedNode() const; bool isSpellCheckingEnabledFor(Node*) const; - void markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping, bool doReplacement); + WEBCORE_EXPORT void markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping, bool doReplacement); void markMisspellings(const VisibleSelection&, RefPtr<Range>& firstMisspellingRange); void markBadGrammar(const VisibleSelection&); void markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection); void markAndReplaceFor(PassRefPtr<SpellCheckRequest>, const Vector<TextCheckingResult>&); bool isOverwriteModeEnabled() const { return m_overwriteModeEnabled; } - void toggleOverwriteModeEnabled(); + WEBCORE_EXPORT void toggleOverwriteModeEnabled(); void markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask, Range* spellingRange, Range* grammarRange); #if PLATFORM(IOS) @@ -251,8 +280,8 @@ public: void changeBackToReplacedString(const String& replacedString); #if !PLATFORM(IOS) - void advanceToNextMisspelling(bool startBeforeSelection = false); -#endif // !PLATFORM(IOS) + WEBCORE_EXPORT void advanceToNextMisspelling(bool startBeforeSelection = false); +#endif void showSpellingGuessPanel(); bool spellingPanelIsShowing(); @@ -275,24 +304,24 @@ public: void showColorPanel(); void toggleBold(); void toggleUnderline(); - void setBaseWritingDirection(WritingDirection); + WEBCORE_EXPORT void setBaseWritingDirection(WritingDirection); // smartInsertDeleteEnabled and selectTrailingWhitespaceEnabled are // mutually exclusive, meaning that enabling one will disable the other. bool smartInsertDeleteEnabled(); bool isSelectTrailingWhitespaceEnabled(); - bool hasBidiSelection() const; + WEBCORE_EXPORT bool hasBidiSelection() const; // international text input composition bool hasComposition() const { return m_compositionNode; } - void setComposition(const String&, const Vector<CompositionUnderline>&, unsigned selectionStart, unsigned selectionEnd); - void confirmComposition(); - void confirmComposition(const String&); // if no existing composition, replaces selection - void cancelComposition(); + WEBCORE_EXPORT void setComposition(const String&, const Vector<CompositionUnderline>&, unsigned selectionStart, unsigned selectionEnd); + WEBCORE_EXPORT void confirmComposition(); + WEBCORE_EXPORT void confirmComposition(const String&); // if no existing composition, replaces selection + WEBCORE_EXPORT void cancelComposition(); bool cancelCompositionIfSelectionIsInvalid(); - PassRefPtr<Range> compositionRange() const; - bool getCompositionSelection(unsigned& selectionStart, unsigned& selectionEnd) const; + WEBCORE_EXPORT RefPtr<Range> compositionRange() const; + WEBCORE_EXPORT bool getCompositionSelection(unsigned& selectionStart, unsigned& selectionEnd) const; // getting international text input composition state (for use by InlineTextBox) Text* compositionNode() const { return m_compositionNode.get(); } @@ -301,12 +330,11 @@ public: bool compositionUsesCustomUnderlines() const { return !m_customCompositionUnderlines.isEmpty(); } const Vector<CompositionUnderline>& customCompositionUnderlines() const { return m_customCompositionUnderlines; } - void setIgnoreCompositionSelectionChange(bool); + enum class RevealSelection { No, Yes }; + WEBCORE_EXPORT void setIgnoreCompositionSelectionChange(bool, RevealSelection shouldRevealExistingSelection = RevealSelection::Yes); bool ignoreCompositionSelectionChange() const { return m_ignoreCompositionSelectionChange; } - void setStartNewKillRingSequence(bool); - - PassRefPtr<Range> rangeForPoint(const IntPoint& windowPoint); + WEBCORE_EXPORT PassRefPtr<Range> rangeForPoint(const IntPoint& windowPoint); void clear(); @@ -317,55 +345,60 @@ public: EditingBehavior behavior() const; - PassRefPtr<Range> selectedRange(); + RefPtr<Range> selectedRange(); #if PLATFORM(IOS) - void confirmMarkedText(); - void setTextAsChildOfElement(const String&, Element*); - void setTextAlignmentForChangedBaseWritingDirection(WritingDirection); - void insertDictationPhrases(PassOwnPtr<Vector<Vector<String> > > dictationPhrases, RetainPtr<id> metadata); - void setDictationPhrasesAsChildOfElement(PassOwnPtr<Vector<Vector<String> > > dictationPhrases, RetainPtr<id> metadata, Element* element); + WEBCORE_EXPORT void confirmMarkedText(); + WEBCORE_EXPORT void setTextAsChildOfElement(const String&, Element&); + WEBCORE_EXPORT void setTextAlignmentForChangedBaseWritingDirection(WritingDirection); + WEBCORE_EXPORT void insertDictationPhrases(Vector<Vector<String>>&& dictationPhrases, RetainPtr<id> metadata); + WEBCORE_EXPORT void setDictationPhrasesAsChildOfElement(const Vector<Vector<String>>& dictationPhrases, RetainPtr<id> metadata, Element&); #endif - void addToKillRing(Range*, bool prepend); + enum class KillRingInsertionMode { PrependText, AppendText }; + void addRangeToKillRing(const Range&, KillRingInsertionMode); + void addTextToKillRing(const String&, KillRingInsertionMode); + void setStartNewKillRingSequence(bool); 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 handleAlternativeTextUIResult(const String& correction); + WEBCORE_EXPORT void handleAlternativeTextUIResult(const String& correction); void dismissCorrectionPanelAsIgnored(); - void pasteAsFragment(PassRefPtr<DocumentFragment>, bool smartReplace, bool matchStyle); - void pasteAsPlainText(const String&, bool smartReplace); + WEBCORE_EXPORT void pasteAsFragment(Ref<DocumentFragment>&&, bool smartReplace, bool matchStyle, MailBlockquoteHandling = MailBlockquoteHandling::RespectBlockquote); + WEBCORE_EXPORT void pasteAsPlainText(const String&, bool smartReplace); // This is only called on the mac where paste is implemented primarily at the WebKit level. - void pasteAsPlainTextBypassingDHTML(); + WEBCORE_EXPORT void pasteAsPlainTextBypassingDHTML(); void clearMisspellingsAndBadGrammar(const VisibleSelection&); void markMisspellingsAndBadGrammar(const VisibleSelection&); Node* findEventTargetFrom(const VisibleSelection& selection) const; - String selectedText() const; - String selectedTextForClipboard() const; - bool findString(const String&, FindOptions); + WEBCORE_EXPORT String selectedText() const; + String selectedTextForDataTransfer() const; + WEBCORE_EXPORT bool findString(const String&, FindOptions); - PassRefPtr<Range> rangeOfString(const String&, Range*, FindOptions); - PassRefPtr<Range> findStringAndScrollToVisible(const String&, Range*, FindOptions); + WEBCORE_EXPORT RefPtr<Range> rangeOfString(const String&, Range*, FindOptions); const VisibleSelection& mark() const; // Mark, to be used as emacs uses it. void setMark(const VisibleSelection&); - void computeAndSetTypingStyle(StyleProperties* , EditAction = EditActionUnspecified); - void applyEditingStyleToBodyElement() const; + void computeAndSetTypingStyle(EditingStyle& , EditAction = EditActionUnspecified); + WEBCORE_EXPORT void computeAndSetTypingStyle(StyleProperties& , EditAction = EditActionUnspecified); + WEBCORE_EXPORT void applyEditingStyleToBodyElement() const; void applyEditingStyleToElement(Element*) const; - IntRect firstRectForRange(Range*) const; + WEBCORE_EXPORT IntRect firstRectForRange(Range*) const; + void selectionWillChange(); void respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions); + WEBCORE_EXPORT void updateEditorUINowIfScheduled(); bool shouldChangeSelection(const VisibleSelection& oldSelection, const VisibleSelection& newSelection, EAffinity, bool stillSelecting) const; - unsigned countMatchesForText(const String&, Range*, FindOptions, unsigned limit, bool markMatches, Vector<RefPtr<Range>>*); + WEBCORE_EXPORT unsigned countMatchesForText(const String&, Range*, FindOptions, unsigned limit, bool markMatches, Vector<RefPtr<Range>>*); bool markedTextMatchesAreHighlighted() const; - void setMarkedTextMatchesAreHighlighted(bool); + WEBCORE_EXPORT void setMarkedTextMatchesAreHighlighted(bool); void textFieldDidBeginEditing(Element*); void textFieldDidEndEditing(Element*); @@ -373,129 +406,166 @@ public: bool doTextFieldCommandFromEvent(Element*, KeyboardEvent*); void textWillBeDeletedInTextField(Element* input); void textDidChangeInTextArea(Element*); - WritingDirection baseWritingDirectionForSelectionStart() const; + WEBCORE_EXPORT WritingDirection baseWritingDirectionForSelectionStart() const; - void replaceSelectionWithFragment(PassRefPtr<DocumentFragment>, bool selectReplacement, bool smartReplace, bool matchStyle); - void replaceSelectionWithText(const String&, bool selectReplacement, bool smartReplace); - bool selectionStartHasMarkerFor(DocumentMarker::MarkerType, int from, int length) const; + WEBCORE_EXPORT void replaceSelectionWithFragment(PassRefPtr<DocumentFragment>, bool selectReplacement, bool smartReplace, bool matchStyle, EditAction = EditActionInsert, MailBlockquoteHandling = MailBlockquoteHandling::RespectBlockquote); + WEBCORE_EXPORT void replaceSelectionWithText(const String&, bool selectReplacement, bool smartReplace, EditAction = EditActionInsert); + WEBCORE_EXPORT 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(); + WEBCORE_EXPORT void simplifyMarkup(Node* startNode, Node* endNode); EditorParagraphSeparator defaultParagraphSeparator() const { return m_defaultParagraphSeparator; } void setDefaultParagraphSeparator(EditorParagraphSeparator separator) { m_defaultParagraphSeparator = separator; } - Vector<String> dictationAlternativesForMarker(const DocumentMarker*); + Vector<String> dictationAlternativesForMarker(const DocumentMarker&); void applyDictationAlternativelternative(const String& alternativeString); - PassRefPtr<Range> avoidIntersectionWithDeleteButtonController(const Range*) const; - VisibleSelection avoidIntersectionWithDeleteButtonController(const VisibleSelection&) const; - #if USE(APPKIT) - void uppercaseWord(); - void lowercaseWord(); - void capitalizeWord(); + WEBCORE_EXPORT void uppercaseWord(); + WEBCORE_EXPORT void lowercaseWord(); + WEBCORE_EXPORT void capitalizeWord(); #endif #if USE(AUTOMATIC_TEXT_REPLACEMENT) - void showSubstitutionsPanel(); - bool substitutionsPanelIsShowing(); - void toggleSmartInsertDelete(); - bool isAutomaticQuoteSubstitutionEnabled(); - void toggleAutomaticQuoteSubstitution(); - bool isAutomaticLinkDetectionEnabled(); - void toggleAutomaticLinkDetection(); - bool isAutomaticDashSubstitutionEnabled(); - void toggleAutomaticDashSubstitution(); - bool isAutomaticTextReplacementEnabled(); - void toggleAutomaticTextReplacement(); - bool isAutomaticSpellingCorrectionEnabled(); - void toggleAutomaticSpellingCorrection(); + WEBCORE_EXPORT void showSubstitutionsPanel(); + WEBCORE_EXPORT bool substitutionsPanelIsShowing(); + WEBCORE_EXPORT void toggleSmartInsertDelete(); + WEBCORE_EXPORT bool isAutomaticQuoteSubstitutionEnabled(); + WEBCORE_EXPORT void toggleAutomaticQuoteSubstitution(); + WEBCORE_EXPORT bool isAutomaticLinkDetectionEnabled(); + WEBCORE_EXPORT void toggleAutomaticLinkDetection(); + WEBCORE_EXPORT bool isAutomaticDashSubstitutionEnabled(); + WEBCORE_EXPORT void toggleAutomaticDashSubstitution(); + WEBCORE_EXPORT bool isAutomaticTextReplacementEnabled(); + WEBCORE_EXPORT void toggleAutomaticTextReplacement(); + WEBCORE_EXPORT bool isAutomaticSpellingCorrectionEnabled(); + WEBCORE_EXPORT void toggleAutomaticSpellingCorrection(); #endif -#if ENABLE(DELETION_UI) - DeleteButtonController& deleteButtonController() const { return *m_deleteButtonController; } -#endif + RefPtr<DocumentFragment> webContentFromPasteboard(Pasteboard&, Range& context, bool allowPlainText, bool& chosePlainText); + + WEBCORE_EXPORT const Font* fontForSelection(bool& hasMultipleFonts) const; + WEBCORE_EXPORT static const RenderStyle* styleForSelectionStart(Frame* , Node *&nodeToRemove); -#if PLATFORM(MAC) - bool insertParagraphSeparatorInQuotedContent(); - const SimpleFontData* fontForSelection(bool&) const; - NSDictionary* fontAttributesForSelectionStart() const; - String stringSelectionForPasteboard(); +#if PLATFORM(COCOA) + void getTextDecorationAttributesRespectingTypingStyle(const RenderStyle&, NSMutableDictionary*) const; + WEBCORE_EXPORT RetainPtr<NSDictionary> fontAttributesForSelectionStart() const; + WEBCORE_EXPORT String stringSelectionForPasteboard(); String stringSelectionForPasteboardWithImageAltText(); - PassRefPtr<DocumentFragment> webContentFromPasteboard(Pasteboard&, Range& context, bool allowPlainText, bool& chosePlainText); #if !PLATFORM(IOS) bool canCopyExcludingStandaloneImages(); void takeFindStringFromSelection(); - void readSelectionFromPasteboard(const String& pasteboardName); - PassRefPtr<SharedBuffer> dataSelectionForPasteboard(const String& pasteboardName); + WEBCORE_EXPORT void readSelectionFromPasteboard(const String& pasteboardName, MailBlockquoteHandling = MailBlockquoteHandling::RespectBlockquote); + WEBCORE_EXPORT void replaceNodeFromPasteboard(Node*, const String& pasteboardName); + WEBCORE_EXPORT RefPtr<SharedBuffer> dataSelectionForPasteboard(const String& pasteboardName); + WEBCORE_EXPORT void applyFontStyles(const String& fontFamily, double fontSize, unsigned fontTraits); #endif // !PLATFORM(IOS) + WEBCORE_EXPORT void replaceSelectionWithAttributedString(NSAttributedString *, MailBlockquoteHandling = MailBlockquoteHandling::RespectBlockquote); #endif -#if PLATFORM(MAC) || PLATFORM(EFL) - void writeSelectionToPasteboard(Pasteboard&); - void writeImageToPasteboard(Pasteboard&, Element& imageElement, const URL&, const String& title); +#if PLATFORM(COCOA) || PLATFORM(GTK) + WEBCORE_EXPORT void writeSelectionToPasteboard(Pasteboard&); + WEBCORE_EXPORT void writeImageToPasteboard(Pasteboard&, Element& imageElement, const URL&, const String& title); #endif +#if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS) + void scanSelectionForTelephoneNumbers(); + const Vector<RefPtr<Range>>& detectedTelephoneNumberRanges() const { return m_detectedTelephoneNumberRanges; } +#endif + + WEBCORE_EXPORT String stringForCandidateRequest() const; + WEBCORE_EXPORT void handleAcceptedCandidate(TextCheckingResult); + WEBCORE_EXPORT RefPtr<Range> contextRangeForCandidateRequest() const; + RefPtr<Range> rangeForTextCheckingResult(const TextCheckingResult&) const; + bool isHandlingAcceptedCandidate() const { return m_isHandlingAcceptedCandidate; } + + void setIsGettingDictionaryPopupInfo(bool b) { m_isGettingDictionaryPopupInfo = b; } + bool isGettingDictionaryPopupInfo() const { return m_isGettingDictionaryPopupInfo; } + + Ref<DocumentFragment> createFragmentForImageAndURL(const String&); + private: class WebContentReader; - explicit Editor(Frame&); - Document& document() const; bool canDeleteRange(Range*) const; bool canSmartReplaceWithPasteboard(Pasteboard&); void pasteAsPlainTextWithPasteboard(Pasteboard&); - void pasteWithPasteboard(Pasteboard*, bool allowPlainText); + void pasteWithPasteboard(Pasteboard*, bool allowPlainText, MailBlockquoteHandling = MailBlockquoteHandling::RespectBlockquote); String plainTextFromPasteboard(const PasteboardPlainText&); void revealSelectionAfterEditingOperation(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, RevealExtentOption = DoNotRevealExtent); void markMisspellingsOrBadGrammar(const VisibleSelection&, bool checkSpelling, RefPtr<Range>& firstMisspellingRange); - TextCheckingTypeMask resolveTextCheckingTypeMask(TextCheckingTypeMask); + TextCheckingTypeMask resolveTextCheckingTypeMask(const Node& rootEditableElement, TextCheckingTypeMask); - String selectedText(TextIteratorBehavior) const; + WEBCORE_EXPORT String selectedText(TextIteratorBehavior) const; void selectComposition(); enum SetCompositionMode { ConfirmComposition, CancelComposition }; void setComposition(const String&, SetCompositionMode); void changeSelectionAfterCommand(const VisibleSelection& newSelection, FrameSelection::SetSelectionOptions); - void notifyComponentsOnChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions); + + enum EditorActionSpecifier { CutAction, CopyAction }; + void performCutOrCopy(EditorActionSpecifier); + + void editorUIUpdateTimerFired(); Node* findEventTargetFromSelection() const; bool unifiedTextCheckerEnabled() const; -#if PLATFORM(MAC) - PassRefPtr<SharedBuffer> selectionInWebArchiveFormat(); - PassRefPtr<Range> adjustedSelectionRange(); - PassRefPtr<DocumentFragment> createFragmentForImageResourceAndAddResource(PassRefPtr<ArchiveResource>); - PassRefPtr<DocumentFragment> createFragmentAndAddResources(NSAttributedString *); + RefPtr<Range> adjustedSelectionRange(); + +#if PLATFORM(COCOA) + RefPtr<SharedBuffer> selectionInWebArchiveFormat(); + String selectionInHTMLFormat(); + RefPtr<SharedBuffer> imageInWebArchiveFormat(Element&); + RefPtr<DocumentFragment> createFragmentForImageResourceAndAddResource(RefPtr<ArchiveResource>&&); + RefPtr<DocumentFragment> createFragmentAndAddResources(NSAttributedString *); + FragmentAndResources createFragment(NSAttributedString *); void fillInUserVisibleForm(PasteboardURL&); + + static RefPtr<SharedBuffer> dataInRTFDFormat(NSAttributedString *); + static RefPtr<SharedBuffer> dataInRTFFormat(NSAttributedString *); #endif + void postTextStateChangeNotificationForCut(const String&, const VisibleSelection&); + Frame& m_frame; -#if ENABLE(DELETION_UI) - OwnPtr<DeleteButtonController> m_deleteButtonController; -#endif RefPtr<CompositeEditCommand> m_lastEditCommand; RefPtr<Text> m_compositionNode; unsigned m_compositionStart; unsigned m_compositionEnd; Vector<CompositionUnderline> m_customCompositionUnderlines; - bool m_ignoreCompositionSelectionChange; - bool m_shouldStartNewKillRingSequence; - bool m_shouldStyleWithCSS; - const OwnPtr<KillRing> m_killRing; - const OwnPtr<SpellChecker> m_spellChecker; - const OwnPtr<AlternativeTextController> m_alternativeTextController; + bool m_ignoreCompositionSelectionChange { false }; + bool m_shouldStartNewKillRingSequence { false }; + bool m_shouldStyleWithCSS { false }; + const std::unique_ptr<KillRing> m_killRing; + const std::unique_ptr<SpellChecker> m_spellChecker; + const std::unique_ptr<AlternativeTextController> m_alternativeTextController; VisibleSelection m_mark; - bool m_areMarkedTextMatchesHighlighted; - EditorParagraphSeparator m_defaultParagraphSeparator; - bool m_overwriteModeEnabled; + bool m_areMarkedTextMatchesHighlighted { false }; + EditorParagraphSeparator m_defaultParagraphSeparator { EditorParagraphSeparatorIsDiv }; + bool m_overwriteModeEnabled { false }; + + VisibleSelection m_oldSelectionForEditorUIUpdate; + Timer m_editorUIUpdateTimer; + bool m_editorUIUpdateTimerShouldCheckSpellingAndGrammar { false }; + bool m_editorUIUpdateTimerWasTriggeredByDictation { false }; + bool m_isHandlingAcceptedCandidate { false }; + +#if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS) + bool shouldDetectTelephoneNumbers(); + void scanRangeForTelephoneNumbers(Range&, const StringView&, Vector<RefPtr<Range>>& markedRanges); + + Timer m_telephoneNumberDetectionUpdateTimer; + Vector<RefPtr<Range>> m_detectedTelephoneNumberRanges; +#endif + + bool m_isGettingDictionaryPopupInfo { false }; }; inline void Editor::setStartNewKillRingSequence(bool flag) @@ -518,20 +588,10 @@ inline bool Editor::markedTextMatchesAreHighlighted() const return m_areMarkedTextMatchesHighlighted; } -#if !ENABLE(DELETION_UI) - -inline PassRefPtr<Range> Editor::avoidIntersectionWithDeleteButtonController(const Range* range) const -{ - return const_cast<Range*>(range); -} - -inline VisibleSelection Editor::avoidIntersectionWithDeleteButtonController(const VisibleSelection& selection) const -{ - return selection; -} - -#endif - } // namespace WebCore -#endif // Editor_h +#if PLATFORM(COCOA) +// This function is declared here but defined in the WebKitLegacy framework. +// FIXME: Get rid of this and change this so it doesn't use WebKitLegacy. +extern "C" void _WebCreateFragment(WebCore::Document&, NSAttributedString *, WebCore::FragmentAndResources&); +#endif diff --git a/Source/WebCore/editing/EditorCommand.cpp b/Source/WebCore/editing/EditorCommand.cpp index 5a916153b..06637d7dc 100644 --- a/Source/WebCore/editing/EditorCommand.cpp +++ b/Source/WebCore/editing/EditorCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006-2008, 2014, 2016 Apple Inc. All rights reserved. * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) * Copyright (C) 2009 Igalia S.L. * @@ -12,10 +12,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -36,7 +36,6 @@ #include "EditorClient.h" #include "Event.h" #include "EventHandler.h" -#include "ExceptionCodePlaceholder.h" #include "FormatBlockCommand.h" #include "Frame.h" #include "FrameView.h" @@ -57,6 +56,7 @@ #include "StyleProperties.h" #include "TypingCommand.h" #include "UnlinkCommand.h" +#include "UserGestureIndicator.h" #include "UserTypingGestureIndicator.h" #include "htmlediting.h" #include "markup.h" @@ -74,17 +74,14 @@ public: TriState (*state)(Frame&, Event*); String (*value)(Frame&, Event*); bool isTextInsertion; - bool allowExecutionWhenDisabled; + bool (*allowExecutionWhenDisabled)(EditorCommandSource); }; -typedef HashMap<String, const EditorInternalCommand*, CaseFoldingHash> CommandMap; +typedef HashMap<String, const EditorInternalCommand*, ASCIICaseInsensitiveHash> CommandMap; static const bool notTextInsertion = false; static const bool isTextInsertion = true; -static const bool allowExecutionWhenDisabled = true; -static const bool doNotAllowExecutionWhenDisabled = false; - // Related to Editor::selectionForCommand. // Certain operations continue to use the target control's selection even if the event handler // already moved the selection outside of the text control. @@ -98,77 +95,46 @@ static Frame* targetFrame(Frame& frame, Event* event) return node->document().frame(); } -static bool applyCommandToFrame(Frame& frame, EditorCommandSource source, EditAction action, StyleProperties* style) +static bool applyCommandToFrame(Frame& frame, EditorCommandSource source, EditAction action, Ref<EditingStyle>&& style) { // FIXME: We don't call shouldApplyStyle when the source is DOM; is there a good reason for that? switch (source) { case CommandFromMenuOrKeyBinding: - frame.editor().applyStyleToSelection(style, action); + frame.editor().applyStyleToSelection(WTFMove(style), action); return true; case CommandFromDOM: case CommandFromDOMWithUserInterface: - frame.editor().applyStyle(style); + frame.editor().applyStyle(WTFMove(style), EditActionUnspecified); return true; } ASSERT_NOT_REACHED(); return false; } -static bool executeApplyStyle(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, const String& propertyValue) +static bool isStylePresent(Editor& editor, CSSPropertyID propertyID, const char* onValue) { - RefPtr<MutableStyleProperties> style = MutableStyleProperties::create(); - style->setProperty(propertyID, propertyValue); - return applyCommandToFrame(frame, source, action, style.get()); + // Style is considered present when + // Mac: present at the beginning of selection + // Windows: present throughout the selection + if (editor.behavior().shouldToggleStyleBasedOnStartOfSelection()) + return editor.selectionStartHasStyle(propertyID, onValue); + return editor.selectionHasStyle(propertyID, onValue) == TrueTriState; } -static bool executeApplyStyle(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, CSSValueID propertyValue) +static bool executeApplyStyle(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, const String& propertyValue) { - RefPtr<MutableStyleProperties> style = MutableStyleProperties::create(); - style->setProperty(propertyID, propertyValue); - return applyCommandToFrame(frame, source, action, style.get()); + return applyCommandToFrame(frame, source, action, EditingStyle::create(propertyID, propertyValue)); } -// 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, CSSPropertyID propertyID, CSSValue* value) +static bool executeApplyStyle(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, CSSValueID propertyValue) { - RefPtr<EditingStyle> selectionStyle = EditingStyle::styleAtSelectionStart(frame.selection().selection()); - if (!selectionStyle || !selectionStyle->style()) - return false; - - RefPtr<CSSValue> selectedCSSValue = selectionStyle->style()->getPropertyCSSValue(propertyID); - String newStyle = ASCIILiteral("none"); - if (selectedCSSValue->isValueList()) { - RefPtr<CSSValueList> selectedCSSValueList = toCSSValueList(selectedCSSValue.get()); - if (!selectedCSSValueList->removeAll(value)) - selectedCSSValueList->append(value); - if (selectedCSSValueList->length()) - newStyle = selectedCSSValueList->cssText(); - - } else if (selectedCSSValue->cssText() == "none") - newStyle = value->cssText(); - - // FIXME: We shouldn't be having to convert new style into text. We should have setPropertyCSSValue. - RefPtr<MutableStyleProperties> newMutableStyle = MutableStyleProperties::create(); - newMutableStyle->setProperty(propertyID, newStyle); - return applyCommandToFrame(frame, source, action, newMutableStyle.get()); + return applyCommandToFrame(frame, source, action, EditingStyle::create(propertyID, propertyValue)); } 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 - // other: present throughout the selection - - bool styleIsPresent; - if (frame.editor().behavior().shouldToggleStyleBasedOnStartOfSelection()) - styleIsPresent = frame.editor().selectionStartHasStyle(propertyID, onValue); - else - styleIsPresent = frame.editor().selectionHasStyle(propertyID, onValue) == TrueTriState; - - RefPtr<EditingStyle> style = EditingStyle::create(propertyID, styleIsPresent ? offValue : onValue); - return applyCommandToFrame(frame, source, action, style->style()); + bool styleIsPresent = isStylePresent(frame.editor(), propertyID, onValue); + return applyCommandToFrame(frame, source, action, EditingStyle::create(propertyID, styleIsPresent ? offValue : onValue)); } static bool executeApplyParagraphStyle(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, const String& propertyValue) @@ -192,18 +158,16 @@ static bool executeApplyParagraphStyle(Frame& frame, EditorCommandSource source, static bool executeInsertFragment(Frame& frame, PassRefPtr<DocumentFragment> fragment) { ASSERT(frame.document()); - applyCommand(ReplaceSelectionCommand::create(*frame.document(), fragment, ReplaceSelectionCommand::PreventNesting, EditActionUnspecified)); + applyCommand(ReplaceSelectionCommand::create(*frame.document(), fragment, ReplaceSelectionCommand::PreventNesting, EditActionInsert)); return true; } -static bool executeInsertNode(Frame& frame, PassRefPtr<Node> content) +static bool executeInsertNode(Frame& frame, Ref<Node>&& content) { - RefPtr<DocumentFragment> fragment = DocumentFragment::create(*frame.document()); - ExceptionCode ec = 0; - fragment->appendChild(content, ec); - if (ec) + auto fragment = DocumentFragment::create(*frame.document()); + if (fragment->appendChild(content).hasException()) return false; - return executeInsertFragment(frame, fragment.release()); + return executeInsertFragment(frame, WTFMove(fragment)); } static bool expandSelectionToGranularity(Frame& frame, TextGranularity granularity) @@ -213,10 +177,10 @@ static bool expandSelectionToGranularity(Frame& frame, TextGranularity granulari RefPtr<Range> newRange = selection.toNormalizedRange(); if (!newRange) return false; - if (newRange->collapsed(IGNORE_EXCEPTION)) + if (newRange->collapsed()) return false; - RefPtr<Range> oldRange = frame.selection().selection().toNormalizedRange(); - EAffinity affinity = frame.selection().affinity(); + RefPtr<Range> oldRange = selection.toNormalizedRange(); + EAffinity affinity = selection.affinity(); if (!frame.editor().client()->shouldChangeSelectedRange(oldRange.get(), newRange.get(), affinity, false)) return false; frame.selection().setSelectedRange(newRange.get(), affinity, true); @@ -251,22 +215,21 @@ static unsigned verticalScrollDistance(Frame& frame) Element* focusedElement = frame.document()->focusedElement(); if (!focusedElement) return 0; - auto renderer = focusedElement->renderer(); - if (!renderer || !renderer->isBox()) + auto* renderer = focusedElement->renderer(); + if (!is<RenderBox>(renderer)) return 0; const RenderStyle& style = renderer->style(); if (!(style.overflowY() == OSCROLL || style.overflowY() == OAUTO || focusedElement->hasEditableStyle())) return 0; - int height = std::min<int>(toRenderBox(renderer)->clientHeight(), frame.view()->visibleHeight()); - return static_cast<unsigned>(std::max(std::max<int>(height * Scrollbar::minFractionToStepWhenPaging(), height - Scrollbar::maxOverlapBetweenPages()), 1)); + int height = std::min<int>(downcast<RenderBox>(*renderer).clientHeight(), frame.view()->visibleHeight()); + return static_cast<unsigned>(Scrollbar::pageStep(height)); } -static RefPtr<Range> unionDOMRanges(Range* a, Range* b) +static RefPtr<Range> unionDOMRanges(Range& a, Range& b) { - Range* start = a->compareBoundaryPoints(Range::START_TO_START, b, ASSERT_NO_EXCEPTION) <= 0 ? a : b; - Range* end = a->compareBoundaryPoints(Range::END_TO_END, b, ASSERT_NO_EXCEPTION) <= 0 ? b : a; - - return Range::create(a->ownerDocument(), start->startContainer(), start->startOffset(), end->endContainer(), end->endOffset()); + Range& start = a.compareBoundaryPoints(Range::START_TO_START, b).releaseReturnValue() <= 0 ? a : b; + Range& end = a.compareBoundaryPoints(Range::END_TO_END, b).releaseReturnValue() <= 0 ? b : a; + return Range::create(a.ownerDocument(), &start.startContainer(), start.startOffset(), &end.endContainer(), end.endOffset()); } // Execute command functions @@ -302,19 +265,17 @@ static bool executeCut(Frame& frame, Event*, EditorCommandSource source, const S return true; } -#if PLATFORM(IOS) static bool executeClearText(Frame& frame, Event*, EditorCommandSource, const String&) { frame.editor().clearText(); return true; } -#endif static bool executeDefaultParagraphSeparator(Frame& frame, Event*, EditorCommandSource, const String& value) { - if (equalIgnoringCase(value, "div")) + if (equalLettersIgnoringASCIICase(value, "div")) frame.editor().setDefaultParagraphSeparator(EditorParagraphSeparatorIsDiv); - else if (equalIgnoringCase(value, "p")) + else if (equalLettersIgnoringASCIICase(value, "p")) frame.editor().setDefaultParagraphSeparator(EditorParagraphSeparatorIsP); return true; @@ -391,8 +352,8 @@ static bool executeDeleteToMark(Frame& frame, Event*, EditorCommandSource, const { RefPtr<Range> mark = frame.editor().mark().toNormalizedRange(); FrameSelection& selection = frame.selection(); - if (mark) { - bool selected = selection.setSelectedRange(unionDOMRanges(mark.get(), frame.editor().selectedRange().get()).get(), DOWNSTREAM, true); + if (mark && frame.editor().selectedRange()) { + bool selected = selection.setSelectedRange(unionDOMRanges(*mark, *frame.editor().selectedRange()).get(), DOWNSTREAM, true); ASSERT(selected); if (!selected) return false; @@ -416,7 +377,7 @@ static bool executeDeleteWordForward(Frame& frame, Event*, EditorCommandSource, static bool executeFindString(Frame& frame, Event*, EditorCommandSource, const String& value) { - return frame.editor().findString(value, CaseInsensitive | WrapAround); + return frame.editor().findString(value, CaseInsensitive | WrapAround | DoNotTraverseFlatTree); } static bool executeFontName(Frame& frame, Event*, EditorCommandSource source, const String& value) @@ -444,18 +405,17 @@ static bool executeForeColor(Frame& frame, Event*, EditorCommandSource source, c static bool executeFormatBlock(Frame& frame, Event*, EditorCommandSource, const String& value) { - String tagName = value.lower(); + String tagName = value.convertToASCIILowercase(); if (tagName[0] == '<' && tagName[tagName.length() - 1] == '>') tagName = tagName.substring(1, tagName.length() - 2); - String localName, prefix; - if (!Document::parseQualifiedName(tagName, prefix, localName, IGNORE_EXCEPTION)) + auto qualifiedTagName = Document::parseQualifiedName(xhtmlNamespaceURI, tagName); + if (qualifiedTagName.hasException()) return false; - QualifiedName qualifiedTagName(prefix, localName, xhtmlNamespaceURI); ASSERT(frame.document()); - RefPtr<FormatBlockCommand> command = FormatBlockCommand::create(*frame.document(), qualifiedTagName); - applyCommand(command); + auto command = FormatBlockCommand::create(*frame.document(), qualifiedTagName.releaseReturnValue()); + applyCommand(command.copyRef()); return command->didApply(); } @@ -492,35 +452,35 @@ static bool executeIndent(Frame& frame, Event*, EditorCommandSource, const Strin static bool executeInsertBacktab(Frame& frame, Event* event, EditorCommandSource, const String&) { - return targetFrame(frame, event)->eventHandler().handleTextInputEvent("\t", event, TextEventInputBackTab); + return targetFrame(frame, event)->eventHandler().handleTextInputEvent(ASCIILiteral("\t"), event, TextEventInputBackTab); } static bool executeInsertHorizontalRule(Frame& frame, Event*, EditorCommandSource, const String& value) { - RefPtr<HTMLHRElement> rule = HTMLHRElement::create(*frame.document()); + Ref<HTMLHRElement> rule = HTMLHRElement::create(*frame.document()); if (!value.isEmpty()) rule->setIdAttribute(value); - return executeInsertNode(frame, rule.release()); + return executeInsertNode(frame, WTFMove(rule)); } static bool executeInsertHTML(Frame& frame, Event*, EditorCommandSource, const String& value) { - return executeInsertFragment(frame, createFragmentFromMarkup(*frame.document(), value, "")); + return executeInsertFragment(frame, createFragmentFromMarkup(*frame.document(), value, emptyString())); } static bool executeInsertImage(Frame& frame, Event*, EditorCommandSource, const String& value) { // FIXME: If userInterface is true, we should display a dialog box and let the user choose a local image. - RefPtr<HTMLImageElement> image = HTMLImageElement::create(*frame.document()); + Ref<HTMLImageElement> image = HTMLImageElement::create(*frame.document()); image->setSrc(value); - return executeInsertNode(frame, image.release()); + return executeInsertNode(frame, WTFMove(image)); } static bool executeInsertLineBreak(Frame& frame, Event* event, EditorCommandSource source, const String&) { switch (source) { case CommandFromMenuOrKeyBinding: - return targetFrame(frame, event)->eventHandler().handleTextInputEvent("\n", event, TextEventInputLineBreak); + return targetFrame(frame, event)->eventHandler().handleTextInputEvent(ASCIILiteral("\n"), event, TextEventInputLineBreak); case CommandFromDOM: case CommandFromDOMWithUserInterface: // Doesn't scroll to make the selection visible, or modify the kill ring. @@ -536,7 +496,7 @@ static bool executeInsertLineBreak(Frame& frame, Event* event, EditorCommandSour static bool executeInsertNewline(Frame& frame, Event* event, EditorCommandSource, const String&) { Frame* targetFrame = WebCore::targetFrame(frame, event); - return targetFrame->eventHandler().handleTextInputEvent("\n", event, targetFrame->editor().canEditRichly() ? TextEventInputKeyboard : TextEventInputLineBreak); + return targetFrame->eventHandler().handleTextInputEvent(ASCIILiteral("\n"), event, targetFrame->editor().canEditRichly() ? TextEventInputKeyboard : TextEventInputLineBreak); } static bool executeInsertNewlineInQuotedContent(Frame& frame, Event*, EditorCommandSource, const String&) @@ -560,7 +520,7 @@ static bool executeInsertParagraph(Frame& frame, Event*, EditorCommandSource, co static bool executeInsertTab(Frame& frame, Event* event, EditorCommandSource, const String&) { - return targetFrame(frame, event)->eventHandler().handleTextInputEvent("\t", event); + return targetFrame(frame, event)->eventHandler().handleTextInputEvent(ASCIILiteral("\t"), event); } static bool executeInsertText(Frame& frame, Event*, EditorCommandSource, const String& value) @@ -578,22 +538,22 @@ static bool executeInsertUnorderedList(Frame& frame, Event*, EditorCommandSource static bool executeJustifyCenter(Frame& frame, Event*, EditorCommandSource source, const String&) { - return executeApplyParagraphStyle(frame, source, EditActionCenter, CSSPropertyTextAlign, "center"); + return executeApplyParagraphStyle(frame, source, EditActionCenter, CSSPropertyTextAlign, ASCIILiteral("center")); } static bool executeJustifyFull(Frame& frame, Event*, EditorCommandSource source, const String&) { - return executeApplyParagraphStyle(frame, source, EditActionJustify, CSSPropertyTextAlign, "justify"); + return executeApplyParagraphStyle(frame, source, EditActionJustify, CSSPropertyTextAlign, ASCIILiteral("justify")); } static bool executeJustifyLeft(Frame& frame, Event*, EditorCommandSource source, const String&) { - return executeApplyParagraphStyle(frame, source, EditActionAlignLeft, CSSPropertyTextAlign, "left"); + return executeApplyParagraphStyle(frame, source, EditActionAlignLeft, CSSPropertyTextAlign, ASCIILiteral("left")); } static bool executeJustifyRight(Frame& frame, Event*, EditorCommandSource source, const String&) { - return executeApplyParagraphStyle(frame, source, EditActionAlignRight, CSSPropertyTextAlign, "right"); + return executeApplyParagraphStyle(frame, source, EditActionAlignRight, CSSPropertyTextAlign, ASCIILiteral("right")); } static bool executeMakeTextWritingDirectionLeftToRight(Frame& frame, Event*, EditorCommandSource, const String&) @@ -970,7 +930,7 @@ static bool executePrint(Frame& frame, Event*, EditorCommandSource, const String Page* page = frame.page(); if (!page) return false; - page->chrome().print(&frame); + page->chrome().print(frame); return true; } @@ -1045,7 +1005,7 @@ static bool executeSelectToMark(Frame& frame, Event*, EditorCommandSource, const systemBeep(); return false; } - frame.selection().setSelectedRange(unionDOMRanges(mark.get(), selection.get()).get(), DOWNSTREAM, true); + frame.selection().setSelectedRange(unionDOMRanges(*mark, *selection).get(), DOWNSTREAM, true); return true; } @@ -1060,36 +1020,44 @@ static bool executeSetMark(Frame& frame, Event*, EditorCommandSource, const Stri return true; } +static TextDecorationChange textDecorationChangeForToggling(Editor& editor, CSSPropertyID propertyID, const char* onValue) +{ + return isStylePresent(editor, propertyID, onValue) ? TextDecorationChange::Remove : TextDecorationChange::Add; +} + static bool executeStrikethrough(Frame& frame, Event*, EditorCommandSource source, const String&) { - RefPtr<CSSPrimitiveValue> lineThrough = CSSPrimitiveValue::createIdentifier(CSSValueLineThrough); - return executeToggleStyleInList(frame, source, EditActionUnderline, CSSPropertyWebkitTextDecorationsInEffect, lineThrough.get()); + Ref<EditingStyle> style = EditingStyle::create(); + style->setStrikeThroughChange(textDecorationChangeForToggling(frame.editor(), CSSPropertyWebkitTextDecorationsInEffect, ASCIILiteral("line-through"))); + // FIXME: Needs a new EditAction! + return applyCommandToFrame(frame, source, EditActionUnderline, WTFMove(style)); } static bool executeStyleWithCSS(Frame& frame, Event*, EditorCommandSource, const String& value) { - frame.editor().setShouldStyleWithCSS(!equalIgnoringCase(value, "false")); + frame.editor().setShouldStyleWithCSS(!equalLettersIgnoringASCIICase(value, "false")); return true; } static bool executeUseCSS(Frame& frame, Event*, EditorCommandSource, const String& value) { - frame.editor().setShouldStyleWithCSS(equalIgnoringCase(value, "false")); + frame.editor().setShouldStyleWithCSS(equalLettersIgnoringASCIICase(value, "false")); return true; } static bool executeSubscript(Frame& frame, Event*, EditorCommandSource source, const String&) { - return executeToggleStyle(frame, source, EditActionSubscript, CSSPropertyVerticalAlign, "baseline", "sub"); + return executeToggleStyle(frame, source, EditActionSubscript, CSSPropertyVerticalAlign, ASCIILiteral("baseline"), ASCIILiteral("sub")); } static bool executeSuperscript(Frame& frame, Event*, EditorCommandSource source, const String&) { - return executeToggleStyle(frame, source, EditActionSuperscript, CSSPropertyVerticalAlign, "baseline", "super"); + return executeToggleStyle(frame, source, EditActionSuperscript, CSSPropertyVerticalAlign, ASCIILiteral("baseline"), ASCIILiteral("super")); } static bool executeSwapWithMark(Frame& frame, Event*, EditorCommandSource, const String&) { + Ref<Frame> protector(frame); const VisibleSelection& mark = frame.editor().mark(); const VisibleSelection& selection = frame.selection().selection(); if (mark.isNone() || selection.isNone()) { @@ -1101,7 +1069,7 @@ static bool executeSwapWithMark(Frame& frame, Event*, EditorCommandSource, const return true; } -#if PLATFORM(MAC) && !PLATFORM(IOS) +#if PLATFORM(MAC) static bool executeTakeFindStringFromSelection(Frame& frame, Event*, EditorCommandSource, const String&) { frame.editor().takeFindStringFromSelection(); @@ -1111,12 +1079,12 @@ static bool executeTakeFindStringFromSelection(Frame& frame, Event*, EditorComma static bool executeToggleBold(Frame& frame, Event*, EditorCommandSource source, const String&) { - return executeToggleStyle(frame, source, EditActionBold, CSSPropertyFontWeight, "normal", "bold"); + return executeToggleStyle(frame, source, EditActionBold, CSSPropertyFontWeight, ASCIILiteral("normal"), ASCIILiteral("bold")); } static bool executeToggleItalic(Frame& frame, Event*, EditorCommandSource source, const String&) { - return executeToggleStyle(frame, source, EditActionItalics, CSSPropertyFontStyle, "normal", "italic"); + return executeToggleStyle(frame, source, EditActionItalics, CSSPropertyFontStyle, ASCIILiteral("normal"), ASCIILiteral("italic")); } static bool executeTranspose(Frame& frame, Event*, EditorCommandSource, const String&) @@ -1127,8 +1095,10 @@ static bool executeTranspose(Frame& frame, Event*, EditorCommandSource, const St static bool executeUnderline(Frame& frame, Event*, EditorCommandSource source, const String&) { - RefPtr<CSSPrimitiveValue> underline = CSSPrimitiveValue::createIdentifier(CSSValueUnderline); - return executeToggleStyleInList(frame, source, EditActionUnderline, CSSPropertyWebkitTextDecorationsInEffect, underline.get()); + Ref<EditingStyle> style = EditingStyle::create(); + TextDecorationChange change = textDecorationChangeForToggling(frame.editor(), CSSPropertyWebkitTextDecorationsInEffect, ASCIILiteral("underline")); + style->setUnderlineChange(change); + return applyCommandToFrame(frame, source, EditActionUnderline, WTFMove(style)); } static bool executeUndo(Frame& frame, Event*, EditorCommandSource, const String&) @@ -1146,7 +1116,7 @@ static bool executeUnlink(Frame& frame, Event*, EditorCommandSource, const Strin static bool executeUnscript(Frame& frame, Event*, EditorCommandSource source, const String&) { - return executeApplyStyle(frame, source, EditActionUnscript, CSSPropertyVerticalAlign, "baseline"); + return executeApplyStyle(frame, source, EditActionUnscript, CSSPropertyVerticalAlign, ASCIILiteral("baseline")); } static bool executeUnselect(Frame& frame, Event*, EditorCommandSource, const String&) @@ -1181,12 +1151,30 @@ static bool supportedFromMenuOrKeyBinding(Frame*) return false; } +static bool defaultValueForSupportedCopyCut(Frame& frame) +{ + auto& settings = frame.settings(); + if (settings.javaScriptCanAccessClipboard()) + return true; + + switch (settings.clipboardAccessPolicy()) { + case ClipboardAccessPolicy::Allow: + case ClipboardAccessPolicy::RequiresUserGesture: + return true; + case ClipboardAccessPolicy::Deny: + return false; + } + + ASSERT_NOT_REACHED(); + return false; +} + static bool supportedCopyCut(Frame* frame) { if (!frame) return false; - bool defaultValue = frame->settings().javaScriptCanAccessClipboard(); + bool defaultValue = defaultValueForSupportedCopyCut(*frame); EditorClient* client = frame->editor().client(); return client ? client->canCopyCut(frame, defaultValue) : defaultValue; @@ -1243,31 +1231,56 @@ static bool enableCaretInEditableText(Frame& frame, Event* event, EditorCommandS return selection.isCaret() && selection.isContentEditable(); } -static bool enabledCopy(Frame& frame, Event*, EditorCommandSource) +static bool allowCopyCutFromDOM(Frame& frame) { -#if !PLATFORM(IOS) - return frame.editor().canDHTMLCopy() || frame.editor().canCopy(); -#else - return frame.editor().canCopy(); -#endif + auto& settings = frame.settings(); + if (settings.javaScriptCanAccessClipboard()) + return true; + + switch (settings.clipboardAccessPolicy()) { + case ClipboardAccessPolicy::Allow: + return true; + case ClipboardAccessPolicy::Deny: + return false; + case ClipboardAccessPolicy::RequiresUserGesture: + return UserGestureIndicator::processingUserGesture(); + } + + ASSERT_NOT_REACHED(); + return false; } -static bool enabledCut(Frame& frame, Event*, EditorCommandSource) +static bool enabledCopy(Frame& frame, Event*, EditorCommandSource source) { -#if !PLATFORM(IOS) - return frame.editor().canDHTMLCut() || frame.editor().canCut(); -#else - return frame.editor().canCut(); -#endif + switch (source) { + case CommandFromMenuOrKeyBinding: + return frame.editor().canDHTMLCopy() || frame.editor().canCopy(); + case CommandFromDOM: + case CommandFromDOMWithUserInterface: + return allowCopyCutFromDOM(frame) && (frame.editor().canDHTMLCopy() || frame.editor().canCopy()); + } + ASSERT_NOT_REACHED(); + return false; +} + +static bool enabledCut(Frame& frame, Event*, EditorCommandSource source) +{ + switch (source) { + case CommandFromMenuOrKeyBinding: + return frame.editor().canDHTMLCut() || frame.editor().canCut(); + case CommandFromDOM: + case CommandFromDOMWithUserInterface: + return allowCopyCutFromDOM(frame) && (frame.editor().canDHTMLCut() || frame.editor().canCut()); + } + ASSERT_NOT_REACHED(); + return false; } -#if PLATFORM(IOS) static bool enabledClearText(Frame& frame, Event*, EditorCommandSource) { UNUSED_PARAM(frame); return false; } -#endif static bool enabledInEditableText(Frame& frame, Event* event, EditorCommandSource) { @@ -1297,7 +1310,8 @@ static bool enabledInEditableTextOrCaretBrowsing(Frame& frame, Event* event, Edi static bool enabledInRichlyEditableText(Frame& frame, Event*, EditorCommandSource) { - return frame.selection().isCaretOrRange() && frame.selection().isContentRichlyEditable() && frame.selection().rootEditableElement(); + const VisibleSelection& selection = frame.selection().selection(); + return selection.isCaretOrRange() && selection.isContentRichlyEditable() && selection.rootEditableElement(); } static bool enabledPaste(Frame& frame, Event*, EditorCommandSource) @@ -1307,12 +1321,12 @@ static bool enabledPaste(Frame& frame, Event*, EditorCommandSource) static bool enabledRangeInEditableText(Frame& frame, Event*, EditorCommandSource) { - return frame.selection().isRange() && frame.selection().isContentEditable(); + return frame.selection().isRange() && frame.selection().selection().isContentEditable(); } static bool enabledRangeInRichlyEditableText(Frame& frame, Event*, EditorCommandSource) { - return frame.selection().isRange() && frame.selection().isContentRichlyEditable(); + return frame.selection().isRange() && frame.selection().selection().isContentRichlyEditable(); } static bool enabledRedo(Frame& frame, Event*, EditorCommandSource) @@ -1320,7 +1334,7 @@ static bool enabledRedo(Frame& frame, Event*, EditorCommandSource) return frame.editor().canRedo(); } -#if PLATFORM(MAC) && !PLATFORM(IOS) +#if PLATFORM(MAC) static bool enabledTakeFindStringFromSelection(Frame& frame, Event*, EditorCommandSource) { return frame.editor().canCopyExcludingStandaloneImages(); @@ -1341,12 +1355,12 @@ static TriState stateNone(Frame&, Event*) static TriState stateBold(Frame& frame, Event*) { - return stateStyle(frame, CSSPropertyFontWeight, "bold"); + return stateStyle(frame, CSSPropertyFontWeight, ASCIILiteral("bold")); } static TriState stateItalic(Frame& frame, Event*) { - return stateStyle(frame, CSSPropertyFontStyle, "italic"); + return stateStyle(frame, CSSPropertyFontStyle, ASCIILiteral("italic")); } static TriState stateOrderedList(Frame& frame, Event*) @@ -1356,7 +1370,7 @@ static TriState stateOrderedList(Frame& frame, Event*) static TriState stateStrikethrough(Frame& frame, Event*) { - return stateStyle(frame, CSSPropertyWebkitTextDecorationsInEffect, "line-through"); + return stateStyle(frame, CSSPropertyWebkitTextDecorationsInEffect, ASCIILiteral("line-through")); } static TriState stateStyleWithCSS(Frame& frame, Event*) @@ -1366,12 +1380,12 @@ static TriState stateStyleWithCSS(Frame& frame, Event*) static TriState stateSubscript(Frame& frame, Event*) { - return stateStyle(frame, CSSPropertyVerticalAlign, "sub"); + return stateStyle(frame, CSSPropertyVerticalAlign, ASCIILiteral("sub")); } static TriState stateSuperscript(Frame& frame, Event*) { - return stateStyle(frame, CSSPropertyVerticalAlign, "super"); + return stateStyle(frame, CSSPropertyVerticalAlign, ASCIILiteral("super")); } static TriState stateTextWritingDirectionLeftToRight(Frame& frame, Event*) @@ -1391,7 +1405,7 @@ static TriState stateTextWritingDirectionRightToLeft(Frame& frame, Event*) static TriState stateUnderline(Frame& frame, Event*) { - return stateStyle(frame, CSSPropertyWebkitTextDecorationsInEffect, "underline"); + return stateStyle(frame, CSSPropertyWebkitTextDecorationsInEffect, ASCIILiteral("underline")); } static TriState stateUnorderedList(Frame& frame, Event*) @@ -1401,22 +1415,22 @@ static TriState stateUnorderedList(Frame& frame, Event*) static TriState stateJustifyCenter(Frame& frame, Event*) { - return stateStyle(frame, CSSPropertyTextAlign, "center"); + return stateStyle(frame, CSSPropertyTextAlign, ASCIILiteral("center")); } static TriState stateJustifyFull(Frame& frame, Event*) { - return stateStyle(frame, CSSPropertyTextAlign, "justify"); + return stateStyle(frame, CSSPropertyTextAlign, ASCIILiteral("justify")); } static TriState stateJustifyLeft(Frame& frame, Event*) { - return stateStyle(frame, CSSPropertyTextAlign, "left"); + return stateStyle(frame, CSSPropertyTextAlign, ASCIILiteral("left")); } static TriState stateJustifyRight(Frame& frame, Event*) { - return stateStyle(frame, CSSPropertyTextAlign, "right"); + return stateStyle(frame, CSSPropertyTextAlign, ASCIILiteral("right")); } // Value functions @@ -1467,14 +1481,40 @@ static String valueForeColor(Frame& frame, Event*) static String valueFormatBlock(Frame& frame, Event*) { const VisibleSelection& selection = frame.selection().selection(); - if (!selection.isNonOrphanedCaretOrRange() || !selection.isContentEditable()) - return ""; + if (selection.isNoneOrOrphaned() || !selection.isContentEditable()) + return emptyString(); Element* formatBlockElement = FormatBlockCommand::elementForFormatBlockCommand(selection.firstRange().get()); if (!formatBlockElement) - return ""; + return emptyString(); return formatBlockElement->localName(); } +// allowExecutionWhenDisabled functions + +static bool allowExecutionWhenDisabled(EditorCommandSource) +{ + return true; +} + +static bool doNotAllowExecutionWhenDisabled(EditorCommandSource) +{ + return false; +} + +static bool allowExecutionWhenDisabledCopyCut(EditorCommandSource source) +{ + switch (source) { + case CommandFromMenuOrKeyBinding: + return true; + case CommandFromDOM: + case CommandFromDOMWithUserInterface: + return false; + } + + ASSERT_NOT_REACHED(); + return false; +} + // Map of functions struct CommandEntry { @@ -1490,11 +1530,11 @@ static const CommandMap& createCommandMap() { "AlignLeft", { executeJustifyLeft, supportedFromMenuOrKeyBinding, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "AlignRight", { executeJustifyRight, supportedFromMenuOrKeyBinding, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "BackColor", { executeBackColor, supported, enabledInRichlyEditableText, stateNone, valueBackColor, notTextInsertion, doNotAllowExecutionWhenDisabled } }, - { "BackwardDelete", { executeDeleteBackward, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, // FIXME: remove BackwardDelete when Safari for Windows stops using it. { "Bold", { executeToggleBold, supported, enabledInRichlyEditableText, stateBold, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, - { "Copy", { executeCopy, supportedCopyCut, enabledCopy, stateNone, valueNull, notTextInsertion, allowExecutionWhenDisabled } }, + { "ClearText", { executeClearText, supported, enabledClearText, stateNone, valueNull, notTextInsertion, allowExecutionWhenDisabled } }, + { "Copy", { executeCopy, supportedCopyCut, enabledCopy, stateNone, valueNull, notTextInsertion, allowExecutionWhenDisabledCopyCut } }, { "CreateLink", { executeCreateLink, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, - { "Cut", { executeCut, supportedCopyCut, enabledCut, stateNone, valueNull, notTextInsertion, allowExecutionWhenDisabled } }, + { "Cut", { executeCut, supportedCopyCut, enabledCut, stateNone, valueNull, notTextInsertion, allowExecutionWhenDisabledCopyCut } }, { "DefaultParagraphSeparator", { executeDefaultParagraphSeparator, supported, enabled, stateNone, valueDefaultParagraphSeparator, notTextInsertion, doNotAllowExecutionWhenDisabled} }, { "Delete", { executeDelete, supported, enabledDelete, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "DeleteBackward", { executeDeleteBackward, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, @@ -1627,12 +1667,9 @@ static const CommandMap& createCommandMap() { "PasteGlobalSelection", { executePasteGlobalSelection, supportedFromMenuOrKeyBinding, enabledPaste, stateNone, valueNull, notTextInsertion, allowExecutionWhenDisabled } }, #endif -#if PLATFORM(MAC) && !PLATFORM(IOS) +#if PLATFORM(MAC) { "TakeFindStringFromSelection", { executeTakeFindStringFromSelection, supportedFromMenuOrKeyBinding, enabledTakeFindStringFromSelection, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, #endif -#if PLATFORM(IOS) - { "ClearText", { executeClearText, supported, enabledClearText, stateNone, valueNull, notTextInsertion, allowExecutionWhenDisabled } }, -#endif }; // These unsupported commands are listed here since they appear in the Microsoft @@ -1683,9 +1720,9 @@ static const CommandMap& createCommandMap() CommandMap& commandMap = *new CommandMap; - for (size_t i = 0; i < WTF_ARRAY_LENGTH(commands); ++i) { - ASSERT(!commandMap.get(commands[i].name)); - commandMap.set(commands[i].name, &commands[i].command); + for (auto& command : commands) { + ASSERT(!commandMap.get(command.name)); + commandMap.set(command.name, &command.command); } return commandMap; @@ -1713,7 +1750,6 @@ bool Editor::commandIsSupportedFromMenuOrKeyBinding(const String& commandName) } Editor::Command::Command() - : m_command(0) { } @@ -1733,10 +1769,14 @@ bool Editor::Command::execute(const String& parameter, Event* triggeringEvent) c { if (!isEnabled(triggeringEvent)) { // Let certain commands be executed when performed explicitly even if they are disabled. - if (!isSupported() || !m_frame || !m_command->allowExecutionWhenDisabled) + if (!allowExecutionWhenDisabled()) return false; } - m_frame->document()->updateLayoutIgnorePendingStylesheets(); + auto document = m_frame->document(); + document->updateLayoutIgnorePendingStylesheets(); + if (m_frame->document() != document) + return false; + return m_command->execute(*m_frame, triggeringEvent, m_source, parameter); } @@ -1779,7 +1819,7 @@ String Editor::Command::value(Event* triggeringEvent) const if (!isSupported() || !m_frame) return String(); if (m_command->value == valueNull && m_command->state != stateNone) - return m_command->state(*m_frame, triggeringEvent) == TrueTriState ? "true" : "false"; + return m_command->state(*m_frame, triggeringEvent) == TrueTriState ? ASCIILiteral("true") : ASCIILiteral("false"); return m_command->value(*m_frame, triggeringEvent); } @@ -1788,4 +1828,11 @@ bool Editor::Command::isTextInsertion() const return m_command && m_command->isTextInsertion; } +bool Editor::Command::allowExecutionWhenDisabled() const +{ + if (!isSupported() || !m_frame) + return false; + return m_command->allowExecutionWhenDisabled(m_source); +} + } // namespace WebCore diff --git a/Source/WebCore/editing/EditorDeleteAction.h b/Source/WebCore/editing/EditorDeleteAction.h index 00bf6835a..1938aab50 100644 --- a/Source/WebCore/editing/EditorDeleteAction.h +++ b/Source/WebCore/editing/EditorDeleteAction.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2006 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef EditorDeleteAction_h -#define EditorDeleteAction_h +#pragma once namespace WebCore { @@ -34,7 +33,4 @@ enum EditorDeleteAction { forwardDeleteKeyAction }; -} // namespace - -#endif - +} // namespace WebCore diff --git a/Source/WebCore/editing/EditorInsertAction.h b/Source/WebCore/editing/EditorInsertAction.h index b8b137d74..f32551e25 100644 --- a/Source/WebCore/editing/EditorInsertAction.h +++ b/Source/WebCore/editing/EditorInsertAction.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2006-2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,18 +23,14 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef EditorInsertAction_h -#define EditorInsertAction_h +#pragma once namespace WebCore { -// This must be kept in sync with WebViewInsertAction defined in WebEditingDelegate.h -enum EditorInsertAction { - EditorInsertActionTyped, - EditorInsertActionPasted, - EditorInsertActionDropped +enum class EditorInsertAction { + Typed, + Pasted, + Dropped, }; -} // namespace - -#endif +} // namespace WebCore diff --git a/Source/WebCore/editing/FindOptions.h b/Source/WebCore/editing/FindOptions.h index ae4aecfa5..bf735ec00 100644 --- a/Source/WebCore/editing/FindOptions.h +++ b/Source/WebCore/editing/FindOptions.h @@ -23,8 +23,7 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef FindOptions_h -#define FindOptions_h +#pragma once namespace WebCore { @@ -36,11 +35,12 @@ enum FindOptionFlag { TreatMedialCapitalAsWordStart = 1 << 2, Backwards = 1 << 3, WrapAround = 1 << 4, - StartInSelection = 1 << 5 + StartInSelection = 1 << 5, + DoNotRevealSelection = 1 << 6, + AtWordEnds = 1 << 7, + DoNotTraverseFlatTree = 1 << 8, }; -typedef unsigned FindOptions; +typedef unsigned short FindOptions; } // namespace WebCore - -#endif // FindOptions_h diff --git a/Source/WebCore/editing/FormatBlockCommand.cpp b/Source/WebCore/editing/FormatBlockCommand.cpp index e94a4ee59..bb3a51d6e 100644 --- a/Source/WebCore/editing/FormatBlockCommand.cpp +++ b/Source/WebCore/editing/FormatBlockCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2006 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -24,15 +24,16 @@ */ #include "config.h" -#include "Element.h" #include "FormatBlockCommand.h" + #include "Document.h" -#include "ExceptionCodePlaceholder.h" -#include "htmlediting.h" +#include "Element.h" #include "HTMLElement.h" #include "HTMLNames.h" #include "Range.h" #include "VisibleUnits.h" +#include "htmlediting.h" +#include <wtf/NeverDestroyed.h> namespace WebCore { @@ -40,9 +41,10 @@ using namespace HTMLNames; static Node* enclosingBlockToSplitTreeTo(Node* startNode); static bool isElementForFormatBlock(const QualifiedName& tagName); + static inline bool isElementForFormatBlock(Node* node) { - return node->isElementNode() && isElementForFormatBlock(toElement(node)->tagQName()); + return is<Element>(*node) && isElementForFormatBlock(downcast<Element>(*node).tagQName()); } FormatBlockCommand::FormatBlockCommand(Document& document, const QualifiedName& tagName) @@ -72,8 +74,8 @@ void FormatBlockCommand::formatRange(const Position& start, const Position& end, if (!root || !refNode) return; if (isElementForFormatBlock(refNode->tagQName()) && start == startOfBlock(start) - && (end == endOfBlock(end) || isNodeVisiblyContainedWithin(refNode, range.get())) - && refNode != root && !root->isDescendantOf(refNode)) { + && (end == endOfBlock(end) || isNodeVisiblyContainedWithin(*refNode, *range)) + && refNode != root && !root->isDescendantOf(*refNode)) { // Already in a block element that only contains the current paragraph if (refNode->hasTagName(tagName())) return; @@ -99,50 +101,50 @@ void FormatBlockCommand::formatRange(const Position& start, const Position& end, Element* FormatBlockCommand::elementForFormatBlockCommand(Range* range) { if (!range) - return 0; + return nullptr; - Node* commonAncestor = range->commonAncestorContainer(IGNORE_EXCEPTION); + Node* commonAncestor = range->commonAncestorContainer(); while (commonAncestor && !isElementForFormatBlock(commonAncestor)) commonAncestor = commonAncestor->parentNode(); if (!commonAncestor) - return 0; + return nullptr; - Element* rootEditableElement = range->startContainer()->rootEditableElement(); + Element* rootEditableElement = range->startContainer().rootEditableElement(); if (!rootEditableElement || commonAncestor->contains(rootEditableElement)) - return 0; + return nullptr; - return commonAncestor->isElementNode() ? toElement(commonAncestor) : 0; + return commonAncestor->isElementNode() ? downcast<Element>(commonAncestor) : nullptr; } bool isElementForFormatBlock(const QualifiedName& tagName) { - DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, blockTags, ()); - if (blockTags.isEmpty()) { - blockTags.add(addressTag); - blockTags.add(articleTag); - blockTags.add(asideTag); - blockTags.add(blockquoteTag); - blockTags.add(ddTag); - blockTags.add(divTag); - blockTags.add(dlTag); - blockTags.add(dtTag); - blockTags.add(footerTag); - blockTags.add(h1Tag); - blockTags.add(h2Tag); - blockTags.add(h3Tag); - blockTags.add(h4Tag); - blockTags.add(h5Tag); - blockTags.add(h6Tag); - blockTags.add(headerTag); - blockTags.add(hgroupTag); - blockTags.add(mainTag); - blockTags.add(navTag); - blockTags.add(pTag); - blockTags.add(preTag); - blockTags.add(sectionTag); + static NeverDestroyed<HashSet<QualifiedName>> blockTags; + if (blockTags.get().isEmpty()) { + blockTags.get().add(addressTag); + blockTags.get().add(articleTag); + blockTags.get().add(asideTag); + blockTags.get().add(blockquoteTag); + blockTags.get().add(ddTag); + blockTags.get().add(divTag); + blockTags.get().add(dlTag); + blockTags.get().add(dtTag); + blockTags.get().add(footerTag); + blockTags.get().add(h1Tag); + blockTags.get().add(h2Tag); + blockTags.get().add(h3Tag); + blockTags.get().add(h4Tag); + blockTags.get().add(h5Tag); + blockTags.get().add(h6Tag); + blockTags.get().add(headerTag); + blockTags.get().add(hgroupTag); + blockTags.get().add(mainTag); + blockTags.get().add(navTag); + blockTags.get().add(pTag); + blockTags.get().add(preTag); + blockTags.get().add(sectionTag); } - return blockTags.contains(tagName); + return blockTags.get().contains(tagName); } Node* enclosingBlockToSplitTreeTo(Node* startNode) @@ -155,7 +157,7 @@ Node* enclosingBlockToSplitTreeTo(Node* startNode) return n; if (isBlock(n)) lastBlock = n; - if (isListElement(n)) + if (isListHTMLElement(n)) return n->parentNode()->hasEditableStyle() ? n->parentNode() : n; } return lastBlock; diff --git a/Source/WebCore/editing/FormatBlockCommand.h b/Source/WebCore/editing/FormatBlockCommand.h index 262536b94..42e4571a4 100644 --- a/Source/WebCore/editing/FormatBlockCommand.h +++ b/Source/WebCore/editing/FormatBlockCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef FormatBlockCommand_h -#define FormatBlockCommand_h +#pragma once #include "ApplyBlockElementCommand.h" #include "EditAction.h" @@ -40,12 +39,12 @@ class VisiblePosition; class FormatBlockCommand : public ApplyBlockElementCommand { public: - static PassRefPtr<FormatBlockCommand> create(Document& document, const QualifiedName& tagName) + static Ref<FormatBlockCommand> create(Document& document, const QualifiedName& tagName) { - return adoptRef(new FormatBlockCommand(document, tagName)); + return adoptRef(*new FormatBlockCommand(document, tagName)); } - virtual bool preservesTypingStyle() const { return true; } + bool preservesTypingStyle() const override { return true; } static Element* elementForFormatBlockCommand(Range*); bool didApply() const { return m_didApply; } @@ -53,13 +52,11 @@ public: private: FormatBlockCommand(Document&, const QualifiedName& tagName); - void formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection); - void formatRange(const Position& start, const Position& end, const Position& endOfSelection, RefPtr<Element>&); - EditAction editingAction() const { return EditActionFormatBlock; } + void formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection) override; + void formatRange(const Position& start, const Position& end, const Position& endOfSelection, RefPtr<Element>&) override; + EditAction editingAction() const override { return EditActionFormatBlock; } bool m_didApply; }; } // namespace WebCore - -#endif // FormatBlockCommand_h diff --git a/Source/WebCore/editing/FrameSelection.cpp b/Source/WebCore/editing/FrameSelection.cpp index c492cf8b7..847479fad 100644 --- a/Source/WebCore/editing/FrameSelection.cpp +++ b/Source/WebCore/editing/FrameSelection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2004, 2008, 2009, 2010, 2014-2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -26,6 +26,7 @@ #include "config.h" #include "FrameSelection.h" +#include "AXObjectCache.h" #include "CharacterData.h" #include "DeleteSelectionCommand.h" #include "Document.h" @@ -33,19 +34,20 @@ #include "EditorClient.h" #include "Element.h" #include "ElementIterator.h" -#include "EventHandler.h" -#include "ExceptionCode.h" +#include "Event.h" +#include "EventNames.h" #include "FloatQuad.h" #include "FocusController.h" #include "Frame.h" #include "FrameTree.h" #include "FrameView.h" #include "GraphicsContext.h" +#include "HTMLBodyElement.h" #include "HTMLFormElement.h" -#include "HTMLFrameElementBase.h" -#include "HTMLInputElement.h" -#include "HTMLSelectElement.h" +#include "HTMLFrameElement.h" +#include "HTMLIFrameElement.h" #include "HTMLNames.h" +#include "HTMLSelectElement.h" #include "HitTestRequest.h" #include "HitTestResult.h" #include "InlineTextBox.h" @@ -74,8 +76,6 @@ #include "RenderStyle.h" #endif -#define EDIT_DEBUG 0 - namespace WebCore { using namespace HTMLNames; @@ -110,17 +110,20 @@ FrameSelection::FrameSelection(Frame* frame) : m_frame(frame) , m_xPosForVerticalArrowNavigation(NoXPosForVerticalArrowNavigation()) , m_granularity(CharacterGranularity) - , m_caretBlinkTimer(this, &FrameSelection::caretBlinkTimerFired) + , m_caretBlinkTimer(*this, &FrameSelection::caretBlinkTimerFired) + , m_appearanceUpdateTimer(*this, &FrameSelection::appearanceUpdateTimerFired) + , m_caretInsidePositionFixed(false) , m_absCaretBoundsDirty(true) , m_caretPaint(true) , m_isCaretBlinkingSuspended(false) , m_focused(frame && frame->page() && frame->page()->focusController().focusedFrame() == frame) , m_shouldShowBlockCursor(false) + , m_pendingSelectionUpdate(false) + , m_shouldRevealSelection(false) + , m_alwaysAlignCursorOnScrollWhenRevealingSelection(false) #if PLATFORM(IOS) , m_updateAppearanceEnabled(false) , m_caretBlinks(true) - , m_closeTypingSuppressions(0) - , m_scrollingSuppressCount(0) #endif { if (shouldAlwaysUseDirectionalSelection(m_frame)) @@ -135,35 +138,40 @@ Element* FrameSelection::rootEditableElementOrDocumentElement() const void FrameSelection::moveTo(const VisiblePosition &pos, EUserTriggered userTriggered, CursorAlignOnScroll align) { - SetSelectionOptions options = CloseTyping | ClearTypingStyle | userTriggered; - setSelection(VisibleSelection(pos.deepEquivalent(), pos.deepEquivalent(), pos.affinity(), m_selection.isDirectional()), options, align); + setSelection(VisibleSelection(pos.deepEquivalent(), pos.deepEquivalent(), pos.affinity(), m_selection.isDirectional()), + defaultSetSelectionOptions(userTriggered), AXTextStateChangeIntent(), align); } void FrameSelection::moveTo(const VisiblePosition &base, const VisiblePosition &extent, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - SetSelectionOptions options = CloseTyping | ClearTypingStyle | userTriggered; - setSelection(VisibleSelection(base.deepEquivalent(), extent.deepEquivalent(), base.affinity(), selectionHasDirection), options); + setSelection(VisibleSelection(base.deepEquivalent(), extent.deepEquivalent(), base.affinity(), selectionHasDirection), defaultSetSelectionOptions(userTriggered)); } void FrameSelection::moveTo(const Position &pos, EAffinity affinity, EUserTriggered userTriggered) { - SetSelectionOptions options = CloseTyping | ClearTypingStyle | userTriggered; - setSelection(VisibleSelection(pos, affinity, m_selection.isDirectional()), options); + setSelection(VisibleSelection(pos, affinity, m_selection.isDirectional()), defaultSetSelectionOptions(userTriggered)); } -void FrameSelection::moveTo(const Range *r, EAffinity affinity, EUserTriggered userTriggered) +void FrameSelection::moveTo(const Range* range) { - SetSelectionOptions options = CloseTyping | ClearTypingStyle | userTriggered; - VisibleSelection selection = r ? VisibleSelection(r->startPosition(), r->endPosition(), affinity) : VisibleSelection(Position(), Position(), affinity); - setSelection(selection, options); + VisibleSelection selection = range ? VisibleSelection(range->startPosition(), range->endPosition()) : VisibleSelection(); + setSelection(selection); } void FrameSelection::moveTo(const Position &base, const Position &extent, EAffinity affinity, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - SetSelectionOptions options = CloseTyping | ClearTypingStyle | userTriggered; - setSelection(VisibleSelection(base, extent, affinity, selectionHasDirection), options); + setSelection(VisibleSelection(base, extent, affinity, selectionHasDirection), defaultSetSelectionOptions(userTriggered)); +} + +void FrameSelection::moveWithoutValidationTo(const Position& base, const Position& extent, bool selectionHasDirection, bool shouldSetFocus, const AXTextStateChangeIntent& intent) +{ + VisibleSelection newSelection; + newSelection.setWithoutValidation(base, extent); + newSelection.setIsDirectional(selectionHasDirection); + AXTextStateChangeIntent newIntent = intent.type == AXTextStateChangeTypeUnknown ? AXTextStateChangeIntent(AXTextStateChangeTypeSelectionMove, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown, false }) : intent; + setSelection(newSelection, defaultSetSelectionOptions() | (shouldSetFocus ? 0 : DoNotSetFocus), newIntent); } void DragCaretController::setCaretPosition(const VisiblePosition& position) @@ -172,7 +180,7 @@ void DragCaretController::setCaretPosition(const VisiblePosition& position) invalidateCaretRect(node); m_position = position; setCaretRectNeedsUpdate(); - Document* document = 0; + Document* document = nullptr; if (Node* node = m_position.deepEquivalent().deprecatedNode()) { invalidateCaretRect(node); document = &node->document(); @@ -220,7 +228,7 @@ static void adjustEndpointsAtBidiBoundary(VisiblePosition& visibleBase, VisibleP } } -void FrameSelection::setNonDirectionalSelectionIfNeeded(const VisibleSelection& passedNewSelection, TextGranularity granularity, +void FrameSelection::setSelectionByMouseIfDifferent(const VisibleSelection& passedNewSelection, TextGranularity granularity, EndPointsAdjustmentMode endpointsAdjustmentMode) { VisibleSelection newSelection = passedNewSelection; @@ -247,144 +255,196 @@ void FrameSelection::setNonDirectionalSelectionIfNeeded(const VisibleSelection& if (m_selection == newSelection || !shouldChangeSelection(newSelection)) return; - setSelection(newSelection, granularity); + + AXTextStateChangeIntent intent; + if (AXObjectCache::accessibilityEnabled() && newSelection.isCaret()) + intent = AXTextStateChangeIntent(AXTextStateChangeTypeSelectionMove, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown, false }); + else + intent = AXTextStateChangeIntent(); + setSelection(newSelection, defaultSetSelectionOptions() | FireSelectEvent, intent, AlignCursorOnScrollIfNeeded, granularity); } -void FrameSelection::setSelection(const VisibleSelection& newSelection, SetSelectionOptions options, CursorAlignOnScroll align, TextGranularity granularity) +bool FrameSelection::setSelectionWithoutUpdatingAppearance(const VisibleSelection& newSelectionPossiblyWithoutDirection, SetSelectionOptions options, CursorAlignOnScroll align, TextGranularity granularity) { bool closeTyping = options & CloseTyping; bool shouldClearTypingStyle = options & ClearTypingStyle; - EUserTriggered userTriggered = selectionOptionsToUserTriggered(options); - VisibleSelection s = newSelection; + VisibleSelection newSelection = newSelectionPossiblyWithoutDirection; if (shouldAlwaysUseDirectionalSelection(m_frame)) - s.setIsDirectional(true); + newSelection.setIsDirectional(true); if (!m_frame) { - m_selection = s; - return; + m_selection = newSelection; + return false; } // <http://bugs.webkit.org/show_bug.cgi?id=23464>: Infinite recursion at FrameSelection::setSelection // if document->frame() == m_frame we can get into an infinite loop - if (s.base().anchorNode()) { - Document& document = s.base().anchorNode()->document(); - if (document.frame() && document.frame() != m_frame && &document != m_frame->document()) { - RefPtr<Frame> guard = document.frame(); - document.frame()->selection().setSelection(s, options, align, granularity); - // It's possible that during the above set selection, this FrameSelection has been modified by - // selectFrameElementInParentIfFullySelected, but that the selection is no longer valid since - // the frame is about to be destroyed. If this is the case, clear our selection. - if (guard->hasOneRef() && !m_selection.isNonOrphanedCaretOrRange()) - clear(); - return; + if (Document* newSelectionDocument = newSelection.base().document()) { + if (RefPtr<Frame> newSelectionFrame = newSelectionDocument->frame()) { + if (newSelectionFrame != m_frame && newSelectionDocument != m_frame->document()) { + newSelectionFrame->selection().setSelection(newSelection, options, AXTextStateChangeIntent(), align, granularity); + // It's possible that during the above set selection, this FrameSelection has been modified by + // selectFrameElementInParentIfFullySelected, but that the selection is no longer valid since + // the frame is about to be destroyed. If this is the case, clear our selection. + if (newSelectionFrame->hasOneRef() && m_selection.isNoneOrOrphaned()) + clear(); + return false; + } } } m_granularity = granularity; -#if PLATFORM(IOS) - if (closeTyping && m_closeTypingSuppressions == 0) -#else if (closeTyping) -#endif TypingCommand::closeTyping(m_frame); if (shouldClearTypingStyle) clearTypingStyle(); - if (m_selection == s) { - // Even if selection was not changed, selection offsets may have been changed. - updateSelectionCachesIfSelectionIsInsideTextFormControl(userTriggered); - return; - } - VisibleSelection oldSelection = m_selection; + bool didMutateSelection = oldSelection != newSelection; + if (didMutateSelection) + m_frame->editor().selectionWillChange(); + + m_selection = newSelection; + + // Selection offsets should increase when LF is inserted before the caret in InsertLineBreakCommand. See <https://webkit.org/b/56061>. + if (HTMLTextFormControlElement* textControl = enclosingTextFormControl(newSelection.start())) + textControl->selectionChanged(options & FireSelectEvent); + + if (!didMutateSelection) + return false; - m_selection = s; setCaretRectNeedsUpdate(); - - if (!s.isNone() && !(options & DoNotSetFocus)) - setFocusedElementIfNeeded(); - if (!(options & DoNotUpdateAppearance)) { -#if ENABLE(TEXT_CARET) - m_frame->document()->updateLayoutIgnorePendingStylesheets(); -#else - m_frame->document()->updateStyleIfNeeded(); -#endif - updateAppearance(); - } + if (!newSelection.isNone() && !(options & DoNotSetFocus)) + setFocusedElementIfNeeded(); // Always clear the x position used for vertical arrow navigation. // It will be restored by the vertical arrow navigation code if necessary. m_xPosForVerticalArrowNavigation = NoXPosForVerticalArrowNavigation(); selectFrameElementInParentIfFullySelected(); - updateSelectionCachesIfSelectionIsInsideTextFormControl(userTriggered); m_frame->editor().respondToChangedSelection(oldSelection, options); - if (userTriggered == UserTriggered) { + m_frame->document()->enqueueDocumentEvent(Event::create(eventNames().selectionchangeEvent, false, false)); + + return true; +} + +void FrameSelection::setSelection(const VisibleSelection& selection, SetSelectionOptions options, AXTextStateChangeIntent intent, CursorAlignOnScroll align, TextGranularity granularity) +{ + RefPtr<Frame> protectedFrame(m_frame); + if (!setSelectionWithoutUpdatingAppearance(selection, options, align, granularity)) + return; + + Document* document = m_frame->document(); + if (!document) + return; + + m_shouldRevealSelection = options & RevealSelection; + m_alwaysAlignCursorOnScrollWhenRevealingSelection = align == AlignCursorOnScrollAlways; + + m_pendingSelectionUpdate = true; + + if (document->hasPendingStyleRecalc()) + return; + + FrameView* frameView = document->view(); + if (frameView && frameView->layoutPending()) + return; + + updateAndRevealSelection(intent); +} + +static void updateSelectionByUpdatingLayoutOrStyle(Frame& frame) +{ +#if ENABLE(TEXT_CARET) + frame.document()->updateLayoutIgnorePendingStylesheets(); +#else + frame.document()->updateStyleIfNeeded(); +#endif +} + +void FrameSelection::setNeedsSelectionUpdate() +{ + m_pendingSelectionUpdate = true; + if (RenderView* view = m_frame->contentRenderer()) + view->clearSelection(); +} + +void FrameSelection::updateAndRevealSelection(const AXTextStateChangeIntent& intent) +{ + if (!m_pendingSelectionUpdate) + return; + + m_pendingSelectionUpdate = false; + + updateAppearance(); + + if (m_shouldRevealSelection) { ScrollAlignment alignment; if (m_frame->editor().behavior().shouldCenterAlignWhenSelectionIsRevealed()) - alignment = (align == AlignCursorOnScrollAlways) ? ScrollAlignment::alignCenterAlways : ScrollAlignment::alignCenterIfNeeded; + alignment = m_alwaysAlignCursorOnScrollWhenRevealingSelection ? ScrollAlignment::alignCenterAlways : ScrollAlignment::alignCenterIfNeeded; else - alignment = (align == AlignCursorOnScrollAlways) ? ScrollAlignment::alignTopAlways : ScrollAlignment::alignToEdgeIfNeeded; + alignment = m_alwaysAlignCursorOnScrollWhenRevealingSelection ? ScrollAlignment::alignTopAlways : ScrollAlignment::alignToEdgeIfNeeded; - revealSelection(alignment, RevealExtent); + revealSelection(SelectionRevealMode::Reveal, alignment, RevealExtent); } -#if HAVE(ACCESSIBILITY) - notifyAccessibilityForSelectionChange(); + + notifyAccessibilityForSelectionChange(intent); + + if (auto* client = m_frame->editor().client()) + client->didChangeSelectionAndUpdateLayout(); +} + +void FrameSelection::updateDataDetectorsForSelection() +{ +#if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS) + m_frame->editor().scanSelectionForTelephoneNumbers(); #endif - m_frame->document()->enqueueDocumentEvent(Event::create(eventNames().selectionchangeEvent, false, false)); } -static bool removingNodeRemovesPosition(Node* node, const Position& position) +static bool removingNodeRemovesPosition(Node& node, const Position& position) { if (!position.anchorNode()) return false; - if (position.anchorNode() == node) + if (position.anchorNode() == &node) return true; - if (!node->isElementNode()) + if (!is<Element>(node)) return false; - Element* element = toElement(node); - return element->containsIncludingShadowDOM(position.anchorNode()); -} - -static void clearRenderViewSelection(const Position& position) -{ - Ref<Document> document(position.anchorNode()->document()); - document->updateStyleIfNeeded(); - if (RenderView* view = document->renderView()) - view->clearSelection(); + return downcast<Element>(node).containsIncludingShadowDOM(position.anchorNode()); } -void DragCaretController::nodeWillBeRemoved(Node* node) +void DragCaretController::nodeWillBeRemoved(Node& node) { - if (!hasCaret() || (node && !node->inDocument())) + if (!hasCaret() || !node.isConnected()) return; if (!removingNodeRemovesPosition(node, m_position.deepEquivalent())) return; - clearRenderViewSelection(m_position.deepEquivalent()); + if (RenderView* view = node.document().renderView()) + view->clearSelection(); + clear(); } -void FrameSelection::nodeWillBeRemoved(Node* node) +void FrameSelection::nodeWillBeRemoved(Node& node) { // There can't be a selection inside a fragment, so if a fragment's node is being removed, // the selection in the document that created the fragment needs no adjustment. - if (isNone() || (node && !node->inDocument())) + if (isNone() || !node.isConnected()) return; respondToNodeModification(node, removingNodeRemovesPosition(node, m_selection.base()), removingNodeRemovesPosition(node, m_selection.extent()), removingNodeRemovesPosition(node, m_selection.start()), removingNodeRemovesPosition(node, m_selection.end())); } -void FrameSelection::respondToNodeModification(Node* node, bool baseRemoved, bool extentRemoved, bool startRemoved, bool endRemoved) +void FrameSelection::respondToNodeModification(Node& node, bool baseRemoved, bool extentRemoved, bool startRemoved, bool endRemoved) { bool clearRenderTreeSelection = false; bool clearDOMTreeSelection = false; @@ -416,19 +476,28 @@ void FrameSelection::respondToNodeModification(Node* node, bool baseRemoved, boo else m_selection.setWithoutValidation(m_selection.end(), m_selection.start()); } else if (RefPtr<Range> range = m_selection.firstRange()) { - ExceptionCode ec = 0; - Range::CompareResults compareResult = range->compareNode(node, ec); - if (!ec && (compareResult == Range::NODE_BEFORE_AND_AFTER || compareResult == Range::NODE_INSIDE)) { - // If we did nothing here, when this node's renderer was destroyed, the rect that it - // occupied would be invalidated, but, selection gaps that change as a result of - // the removal wouldn't be invalidated. - // FIXME: Don't do so much unnecessary invalidation. - clearRenderTreeSelection = true; + auto compareNodeResult = range->compareNode(node); + if (!compareNodeResult.hasException()) { + auto compareResult = compareNodeResult.releaseReturnValue(); + if (compareResult == Range::NODE_BEFORE_AND_AFTER || compareResult == Range::NODE_INSIDE) { + // If we did nothing here, when this node's renderer was destroyed, the rect that it + // occupied would be invalidated, but, selection gaps that change as a result of + // the removal wouldn't be invalidated. + // FIXME: Don't do so much unnecessary invalidation. + clearRenderTreeSelection = true; + } } } - if (clearRenderTreeSelection) - clearRenderViewSelection(m_selection.start()); + if (clearRenderTreeSelection) { + if (auto* renderView = node.document().renderView()) { + renderView->clearSelection(); + + // Trigger a selection update so the selection will be set again. + m_pendingSelectionUpdate = true; + renderView->setNeedsLayout(); + } + } if (clearDOMTreeSelection) setSelection(VisibleSelection(), DoNotSetFocus); @@ -457,7 +526,7 @@ static void updatePositionAfterAdoptingTextReplacement(Position& position, Chara void FrameSelection::textWasReplaced(CharacterData* node, unsigned offset, unsigned oldLength, unsigned newLength) { // The fragment check is a performance optimization. See http://trac.webkit.org/changeset/30062. - if (isNone() || !node || !node->inDocument()) + if (isNone() || !node || !node->isConnected()) return; Position base = m_selection.base(); @@ -478,7 +547,6 @@ void FrameSelection::textWasReplaced(CharacterData* node, unsigned offset, unsig else newSelection.setWithoutValidation(start, end); - m_frame->document()->updateLayout(); setSelection(newSelection, DoNotSetFocus); } } @@ -490,8 +558,8 @@ TextDirection FrameSelection::directionOfEnclosingBlock() TextDirection FrameSelection::directionOfSelection() { - InlineBox* startBox = 0; - InlineBox* endBox = 0; + InlineBox* startBox = nullptr; + InlineBox* endBox = nullptr; int unusedOffset; // Cache the VisiblePositions because visibleStart() and visibleEnd() // can cause layout, which has the potential to invalidate lineboxes. @@ -558,14 +626,14 @@ void FrameSelection::willBeModified(EAlteration alter, SelectionDirection direct VisiblePosition FrameSelection::positionForPlatform(bool isGetStart) const { - if (m_frame && m_frame->settings().editingBehaviorType() == EditingMacBehavior) - return isGetStart ? m_selection.visibleStart() : m_selection.visibleEnd(); - // Linux and Windows always extend selections from the extent endpoint. // FIXME: VisibleSelection should be fixed to ensure as an invariant that // base/extent always point to the same nodes as start/end, but which points // to which depends on the value of isBaseFirst. Then this can be changed // to just return m_sel.extent(). - return m_selection.isBaseFirst() ? m_selection.visibleEnd() : m_selection.visibleStart(); + if (m_frame && m_frame->editor().behavior().shouldAlwaysExtendSelectionFromExtentEndpoint()) + return m_selection.isBaseFirst() ? m_selection.visibleEnd() : m_selection.visibleStart(); + + return isGetStart ? m_selection.visibleStart() : m_selection.visibleEnd(); } VisiblePosition FrameSelection::startForPlatform() const @@ -643,11 +711,9 @@ VisiblePosition FrameSelection::modifyExtendingRight(TextGranularity granularity // FIXME: implement all of the above? pos = modifyExtendingForward(granularity); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif } #if ENABLE(USERSELECT_ALL) adjustPositionForUserSelectAll(pos, directionOfEnclosingBlock() == LTR); @@ -674,11 +740,9 @@ VisiblePosition FrameSelection::modifyExtendingForward(TextGranularity granulari case ParagraphGranularity: pos = nextParagraphPosition(pos, lineDirectionPointForBlockDirectionNavigation(EXTENT)); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif case SentenceBoundary: pos = endOfSentence(endForPlatform()); break; @@ -702,8 +766,10 @@ VisiblePosition FrameSelection::modifyExtendingForward(TextGranularity granulari return pos; } -VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity) +VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity, bool* reachedBoundary) { + if (reachedBoundary) + *reachedBoundary = false; VisiblePosition pos; switch (granularity) { case CharacterGranularity: @@ -713,13 +779,14 @@ VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity) else pos = VisiblePosition(m_selection.start(), m_selection.affinity()); } else - pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).right(true); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).right(true, reachedBoundary); break; case WordGranularity: { - // 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); + VisiblePosition currentPosition(m_selection.extent(), m_selection.affinity()); + pos = rightWordPosition(currentPosition, skipsSpaceWhenMovingRight); + if (reachedBoundary) + *reachedBoundary = pos == currentPosition; break; } case SentenceGranularity: @@ -729,22 +796,38 @@ VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity) case ParagraphBoundary: case DocumentBoundary: // FIXME: Implement all of the above. - pos = modifyMovingForward(granularity); + pos = modifyMovingForward(granularity, reachedBoundary); break; case LineBoundary: - pos = rightBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock()); + pos = rightBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock(), reachedBoundary); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif } return pos; } -VisiblePosition FrameSelection::modifyMovingForward(TextGranularity granularity) +VisiblePosition FrameSelection::modifyMovingForward(TextGranularity granularity, bool* reachedBoundary) { + if (reachedBoundary) + *reachedBoundary = false; + VisiblePosition currentPosition; + switch (granularity) { + case WordGranularity: + case SentenceGranularity: + currentPosition = VisiblePosition(m_selection.extent(), m_selection.affinity()); + break; + case LineGranularity: + case ParagraphGranularity: + case SentenceBoundary: + case ParagraphBoundary: + case DocumentBoundary: + currentPosition = endForPlatform(); + break; + default: + break; + } VisiblePosition pos; // FIXME: Stay in editable content for the less common granularities. switch (granularity) { @@ -752,47 +835,59 @@ VisiblePosition FrameSelection::modifyMovingForward(TextGranularity granularity) if (isRange()) pos = VisiblePosition(m_selection.end(), m_selection.affinity()); else - pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).next(CannotCrossEditingBoundary); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).next(CannotCrossEditingBoundary, reachedBoundary); break; case WordGranularity: - pos = nextWordPositionForPlatform(VisiblePosition(m_selection.extent(), m_selection.affinity())); + pos = nextWordPositionForPlatform(currentPosition); break; case SentenceGranularity: - pos = nextSentencePosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); + pos = nextSentencePosition(currentPosition); break; case LineGranularity: { // down-arrowing from a range selection that ends at the start of a line needs // to leave the selection at that line start (no need to call nextLinePosition!) - pos = endForPlatform(); + pos = currentPosition; if (!isRange() || !isStartOfLine(pos)) pos = nextLinePosition(pos, lineDirectionPointForBlockDirectionNavigation(START)); break; } case ParagraphGranularity: - pos = nextParagraphPosition(endForPlatform(), lineDirectionPointForBlockDirectionNavigation(START)); + pos = nextParagraphPosition(currentPosition, lineDirectionPointForBlockDirectionNavigation(START)); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif case SentenceBoundary: - pos = endOfSentence(endForPlatform()); + pos = endOfSentence(currentPosition); break; case LineBoundary: - pos = logicalEndOfLine(endForPlatform()); + pos = logicalEndOfLine(endForPlatform(), reachedBoundary); break; case ParagraphBoundary: - pos = endOfParagraph(endForPlatform()); + pos = endOfParagraph(currentPosition); break; case DocumentBoundary: - pos = endForPlatform(); + pos = currentPosition; if (isEditablePosition(pos.deepEquivalent())) pos = endOfEditableContent(pos); else pos = endOfDocument(pos); break; } + switch (granularity) { + case WordGranularity: + case SentenceGranularity: + case LineGranularity: + case ParagraphGranularity: + case SentenceBoundary: + case ParagraphBoundary: + case DocumentBoundary: + if (reachedBoundary) + *reachedBoundary = pos == currentPosition; + break; + default: + break; + } return pos; } @@ -832,11 +927,9 @@ VisiblePosition FrameSelection::modifyExtendingLeft(TextGranularity granularity) case DocumentBoundary: pos = modifyExtendingBackward(granularity); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif } #if ENABLE(USERSELECT_ALL) adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == LTR)); @@ -884,11 +977,9 @@ VisiblePosition FrameSelection::modifyExtendingBackward(TextGranularity granular else pos = startOfDocument(pos); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif } #if ENABLE(USERSELECT_ALL) adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == LTR)); @@ -896,8 +987,10 @@ VisiblePosition FrameSelection::modifyExtendingBackward(TextGranularity granular return pos; } -VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity) +VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity, bool* reachedBoundary) { + if (reachedBoundary) + *reachedBoundary = false; VisiblePosition pos; switch (granularity) { case CharacterGranularity: @@ -907,11 +1000,14 @@ VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity) else pos = VisiblePosition(m_selection.end(), m_selection.affinity()); else - pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).left(true); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).left(true, reachedBoundary); break; case WordGranularity: { bool skipsSpaceWhenMovingRight = m_frame && m_frame->editor().behavior().shouldSkipSpaceWhenMovingRight(); - pos = leftWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity()), skipsSpaceWhenMovingRight); + VisiblePosition currentPosition(m_selection.extent(), m_selection.affinity()); + pos = leftWordPosition(currentPosition, skipsSpaceWhenMovingRight); + if (reachedBoundary) + *reachedBoundary = pos == currentPosition; break; } case SentenceGranularity: @@ -921,63 +1017,91 @@ VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity) case ParagraphBoundary: case DocumentBoundary: // FIXME: Implement all of the above. - pos = modifyMovingBackward(granularity); + pos = modifyMovingBackward(granularity, reachedBoundary); break; case LineBoundary: - pos = leftBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock()); + pos = leftBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock(), reachedBoundary); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif } return pos; } -VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity) +VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity, bool* reachedBoundary) { + if (reachedBoundary) + *reachedBoundary = false; + VisiblePosition currentPosition; + switch (granularity) { + case WordGranularity: + case SentenceGranularity: + currentPosition = VisiblePosition(m_selection.extent(), m_selection.affinity()); + break; + case LineGranularity: + case ParagraphGranularity: + case SentenceBoundary: + case ParagraphBoundary: + case DocumentBoundary: + currentPosition = startForPlatform(); + break; + default: + break; + } VisiblePosition pos; switch (granularity) { case CharacterGranularity: if (isRange()) pos = VisiblePosition(m_selection.start(), m_selection.affinity()); else - pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).previous(CannotCrossEditingBoundary); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).previous(CannotCrossEditingBoundary, reachedBoundary); break; case WordGranularity: - pos = previousWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); + pos = previousWordPosition(currentPosition); break; case SentenceGranularity: - pos = previousSentencePosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); + pos = previousSentencePosition(currentPosition); break; case LineGranularity: - pos = previousLinePosition(startForPlatform(), lineDirectionPointForBlockDirectionNavigation(START)); + pos = previousLinePosition(currentPosition, lineDirectionPointForBlockDirectionNavigation(START)); break; case ParagraphGranularity: - pos = previousParagraphPosition(startForPlatform(), lineDirectionPointForBlockDirectionNavigation(START)); + pos = previousParagraphPosition(currentPosition, lineDirectionPointForBlockDirectionNavigation(START)); break; case SentenceBoundary: - pos = startOfSentence(startForPlatform()); + pos = startOfSentence(currentPosition); break; case LineBoundary: - pos = logicalStartOfLine(startForPlatform()); + pos = logicalStartOfLine(startForPlatform(), reachedBoundary); break; case ParagraphBoundary: - pos = startOfParagraph(startForPlatform()); + pos = startOfParagraph(currentPosition); break; case DocumentBoundary: - pos = startForPlatform(); + pos = currentPosition; if (isEditablePosition(pos.deepEquivalent())) pos = startOfEditableContent(pos); else pos = startOfDocument(pos); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif + } + switch (granularity) { + case WordGranularity: + case SentenceGranularity: + case LineGranularity: + case ParagraphGranularity: + case SentenceBoundary: + case ParagraphBoundary: + case DocumentBoundary: + if (reachedBoundary) + *reachedBoundary = pos == currentPosition; + break; + default: + break; } return pos; } @@ -985,7 +1109,137 @@ VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity static bool isBoundary(TextGranularity granularity) { return granularity == LineBoundary || granularity == ParagraphBoundary || granularity == DocumentBoundary; -} +} + +AXTextStateChangeIntent FrameSelection::textSelectionIntent(EAlteration alter, SelectionDirection direction, TextGranularity granularity) +{ + AXTextStateChangeIntent intent = AXTextStateChangeIntent(); + bool flip = false; + if (alter == FrameSelection::AlterationMove) { + intent.type = AXTextStateChangeTypeSelectionMove; + flip = isRange() && directionOfSelection() == RTL; + } else + intent.type = AXTextStateChangeTypeSelectionExtend; + switch (granularity) { + case CharacterGranularity: + intent.selection.granularity = AXTextSelectionGranularityCharacter; + break; + case WordGranularity: + intent.selection.granularity = AXTextSelectionGranularityWord; + break; + case SentenceGranularity: + case SentenceBoundary: + intent.selection.granularity = AXTextSelectionGranularitySentence; + break; + case LineGranularity: + case LineBoundary: + intent.selection.granularity = AXTextSelectionGranularityLine; + break; + case ParagraphGranularity: + case ParagraphBoundary: + intent.selection.granularity = AXTextSelectionGranularityParagraph; + break; + case DocumentGranularity: + case DocumentBoundary: + intent.selection.granularity = AXTextSelectionGranularityDocument; + break; + } + bool boundary = false; + switch (granularity) { + case CharacterGranularity: + case WordGranularity: + case SentenceGranularity: + case LineGranularity: + case ParagraphGranularity: + case DocumentGranularity: + break; + case SentenceBoundary: + case LineBoundary: + case ParagraphBoundary: + case DocumentBoundary: + boundary = true; + break; + } + switch (direction) { + case DirectionRight: + case DirectionForward: + if (boundary) + intent.selection.direction = flip ? AXTextSelectionDirectionBeginning : AXTextSelectionDirectionEnd; + else + intent.selection.direction = flip ? AXTextSelectionDirectionPrevious : AXTextSelectionDirectionNext; + break; + case DirectionLeft: + case DirectionBackward: + if (boundary) + intent.selection.direction = flip ? AXTextSelectionDirectionEnd : AXTextSelectionDirectionBeginning; + else + intent.selection.direction = flip ? AXTextSelectionDirectionNext : AXTextSelectionDirectionPrevious; + break; + } + return intent; +} + +static AXTextSelection textSelectionWithDirectionAndGranularity(SelectionDirection direction, TextGranularity granularity) +{ + // FIXME: Account for BIDI in DirectionRight & DirectionLeft. (In a RTL block, Right would map to Previous/Beginning and Left to Next/End.) + AXTextSelectionDirection intentDirection = AXTextSelectionDirectionUnknown; + switch (direction) { + case DirectionForward: + intentDirection = AXTextSelectionDirectionNext; + break; + case DirectionRight: + intentDirection = AXTextSelectionDirectionNext; + break; + case DirectionBackward: + intentDirection = AXTextSelectionDirectionPrevious; + break; + case DirectionLeft: + intentDirection = AXTextSelectionDirectionPrevious; + break; + } + AXTextSelectionGranularity intentGranularity = AXTextSelectionGranularityUnknown; + switch (granularity) { + case CharacterGranularity: + intentGranularity = AXTextSelectionGranularityCharacter; + break; + case WordGranularity: + intentGranularity = AXTextSelectionGranularityWord; + break; + case SentenceGranularity: + case SentenceBoundary: // FIXME: Boundary should affect direction. + intentGranularity = AXTextSelectionGranularitySentence; + break; + case LineGranularity: + intentGranularity = AXTextSelectionGranularityLine; + break; + case ParagraphGranularity: + case ParagraphBoundary: // FIXME: Boundary should affect direction. + intentGranularity = AXTextSelectionGranularityParagraph; + break; + case DocumentGranularity: + case DocumentBoundary: // FIXME: Boundary should affect direction. + intentGranularity = AXTextSelectionGranularityDocument; + break; + case LineBoundary: + intentGranularity = AXTextSelectionGranularityLine; + switch (direction) { + case DirectionForward: + intentDirection = AXTextSelectionDirectionEnd; + break; + case DirectionRight: + intentDirection = AXTextSelectionDirectionEnd; + break; + case DirectionBackward: + intentDirection = AXTextSelectionDirectionBeginning; + break; + case DirectionLeft: + intentDirection = AXTextSelectionDirectionBeginning; + break; + } + break; + } + return { intentDirection, intentGranularity, false }; +} bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, TextGranularity granularity, EUserTriggered userTriggered) { @@ -1004,13 +1258,14 @@ bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, Tex willBeModified(alter, direction); + bool reachedBoundary = false; bool wasRange = m_selection.isRange(); Position originalStartPosition = m_selection.start(); VisiblePosition position; switch (direction) { case DirectionRight: if (alter == AlterationMove) - position = modifyMovingRight(granularity); + position = modifyMovingRight(granularity, &reachedBoundary); else position = modifyExtendingRight(granularity); break; @@ -1018,11 +1273,11 @@ bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, Tex if (alter == AlterationExtend) position = modifyExtendingForward(granularity); else - position = modifyMovingForward(granularity); + position = modifyMovingForward(granularity, &reachedBoundary); break; case DirectionLeft: if (alter == AlterationMove) - position = modifyMovingLeft(granularity); + position = modifyMovingLeft(granularity, &reachedBoundary); else position = modifyExtendingLeft(granularity); break; @@ -1030,10 +1285,15 @@ bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, Tex if (alter == AlterationExtend) position = modifyExtendingBackward(granularity); else - position = modifyMovingBackward(granularity); + position = modifyMovingBackward(granularity, &reachedBoundary); break; } + if (reachedBoundary && !isRange() && userTriggered == UserTriggered && m_frame && AXObjectCache::accessibilityEnabled()) { + notifyAccessibilityForSelectionChange({ AXTextStateChangeTypeSelectionBoundary, textSelectionWithDirectionAndGranularity(direction, granularity) }); + return true; + } + if (position.isNull()) return false; @@ -1041,6 +1301,11 @@ bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, Tex if (!wasRange && alter == AlterationMove && position == originalStartPosition) return false; + if (m_frame && AXObjectCache::accessibilityEnabled()) { + if (AXObjectCache* cache = m_frame->document()->existingAXObjectCache()) + cache->setTextSelectionIntent(textSelectionIntent(alter, direction, granularity)); + } + // Some of the above operations set an xPosForVerticalArrowNavigation. // Setting a selection will clear it, so save it to possibly restore later. // Note: the START position type is arbitrary because it is unused, it would be @@ -1241,8 +1506,8 @@ void FrameSelection::prepareForDestruction() if (view) view->clearSelection(); - setSelection(VisibleSelection(), CloseTyping | ClearTypingStyle | DoNotUpdateAppearance); - m_previousCaretNode.clear(); + setSelectionWithoutUpdatingAppearance(VisibleSelection(), defaultSetSelectionOptions(), AlignCursorOnScrollIfNeeded, CharacterGranularity); + m_previousCaretNode = nullptr; } void FrameSelection::setStart(const VisiblePosition &pos, EUserTriggered trigger) @@ -1264,25 +1529,25 @@ void FrameSelection::setEnd(const VisiblePosition &pos, EUserTriggered trigger) void FrameSelection::setBase(const VisiblePosition &pos, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - setSelection(VisibleSelection(pos.deepEquivalent(), m_selection.extent(), pos.affinity(), selectionHasDirection), CloseTyping | ClearTypingStyle | userTriggered); + setSelection(VisibleSelection(pos.deepEquivalent(), m_selection.extent(), pos.affinity(), selectionHasDirection), defaultSetSelectionOptions(userTriggered)); } void FrameSelection::setExtent(const VisiblePosition &pos, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - setSelection(VisibleSelection(m_selection.base(), pos.deepEquivalent(), pos.affinity(), selectionHasDirection), CloseTyping | ClearTypingStyle | userTriggered); + setSelection(VisibleSelection(m_selection.base(), pos.deepEquivalent(), pos.affinity(), selectionHasDirection), defaultSetSelectionOptions(userTriggered)); } void FrameSelection::setBase(const Position &pos, EAffinity affinity, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - setSelection(VisibleSelection(pos, m_selection.extent(), affinity, selectionHasDirection), CloseTyping | ClearTypingStyle | userTriggered); + setSelection(VisibleSelection(pos, m_selection.extent(), affinity, selectionHasDirection), defaultSetSelectionOptions(userTriggered)); } void FrameSelection::setExtent(const Position &pos, EAffinity affinity, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - setSelection(VisibleSelection(m_selection.base(), pos, affinity, selectionHasDirection), CloseTyping | ClearTypingStyle | userTriggered); + setSelection(VisibleSelection(m_selection.base(), pos, affinity, selectionHasDirection), defaultSetSelectionOptions(userTriggered)); } void CaretBase::clearCaretRect() @@ -1290,71 +1555,23 @@ void CaretBase::clearCaretRect() m_caretLocalRect = LayoutRect(); } -static inline bool caretRendersInsideNode(Node* node) -{ - return node && !isTableElement(node) && !editingIgnoresContent(node); -} - -static RenderObject* caretRenderer(Node* node) -{ - if (!node) - return 0; - - RenderObject* renderer = node->renderer(); - if (!renderer) - return 0; - - // if caretNode is a block and caret is inside it then caret should be painted by that block - bool paintedByBlock = renderer->isRenderBlockFlow() && caretRendersInsideNode(node); - return paintedByBlock ? renderer : renderer->containingBlock(); -} - bool CaretBase::updateCaretRect(Document* document, const VisiblePosition& caretPosition) { - document->updateStyleIfNeeded(); - m_caretLocalRect = LayoutRect(); - + document->updateLayoutIgnorePendingStylesheets(); m_caretRectNeedsUpdate = false; - - if (caretPosition.isNull()) - return false; - - ASSERT(caretPosition.deepEquivalent().deprecatedNode()->renderer()); - - // First compute a rect local to the renderer at the selection start. - RenderObject* renderer; - LayoutRect localRect = caretPosition.localCaretRect(renderer); - - // Get the renderer that will be responsible for painting the caret - // (which is either the renderer we just found, or one of its containers). - RenderObject* caretPainter = caretRenderer(caretPosition.deepEquivalent().deprecatedNode()); - - // Compute an offset between the renderer and the caretPainter. - bool unrooted = false; - while (renderer != caretPainter) { - RenderObject* containerObject = renderer->container(); - if (!containerObject) { - unrooted = true; - break; - } - localRect.move(renderer->offsetFromContainer(containerObject, localRect.location())); - renderer = containerObject; - } - - if (!unrooted) - m_caretLocalRect = localRect; - - return true; + RenderBlock* renderer; + m_caretLocalRect = localCaretRectInRendererForCaretPainting(caretPosition, renderer); + return !m_caretLocalRect.isEmpty(); } -RenderObject* FrameSelection::caretRenderer() const +RenderBlock* FrameSelection::caretRendererWithoutUpdatingLayout() const { - return WebCore::caretRenderer(m_selection.start().deprecatedNode()); + return rendererForCaretPainting(m_selection.start().deprecatedNode()); } -RenderObject* DragCaretController::caretRenderer() const +RenderBlock* DragCaretController::caretRenderer() const { - return WebCore::caretRenderer(m_position.deepEquivalent().deprecatedNode()); + return rendererForCaretPainting(m_position.deepEquivalent().deprecatedNode()); } static bool isNonOrphanedCaret(const VisibleSelection& selection) @@ -1362,43 +1579,21 @@ static bool isNonOrphanedCaret(const VisibleSelection& selection) return selection.isCaret() && !selection.start().isOrphan() && !selection.end().isOrphan(); } -LayoutRect FrameSelection::localCaretRect() +IntRect FrameSelection::absoluteCaretBounds(bool* insideFixed) { - if (shouldUpdateCaretRect()) { - if (!isNonOrphanedCaret(m_selection)) - clearCaretRect(); - else if (updateCaretRect(m_frame->document(), VisiblePosition(m_selection.start(), m_selection.affinity()))) - m_absCaretBoundsDirty = true; - } - - return localCaretRectWithoutUpdate(); -} - -IntRect CaretBase::absoluteBoundsForLocalRect(Node* node, const LayoutRect& rect) const -{ - RenderObject* caretPainter = caretRenderer(node); - if (!caretPainter) + if (!m_frame) return IntRect(); - - LayoutRect localRect(rect); - if (caretPainter->isBox()) - toRenderBox(caretPainter)->flipForWritingMode(localRect); - return caretPainter->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox(); -} - -IntRect FrameSelection::absoluteCaretBounds() -{ + updateSelectionByUpdatingLayoutOrStyle(*m_frame); recomputeCaretRect(); + if (insideFixed) + *insideFixed = m_caretInsidePositionFixed; return m_absCaretBounds; } static void repaintCaretForLocalRect(Node* node, const LayoutRect& rect) { - RenderObject* caretPainter = caretRenderer(node); - if (!caretPainter) - return; - - caretPainter->repaintRectangle(rect); + if (auto* caretPainter = rendererForCaretPainting(node)) + caretPainter->repaintRectangle(rect); } bool FrameSelection::recomputeCaretRect() @@ -1413,16 +1608,33 @@ bool FrameSelection::recomputeCaretRect() if (!v) return false; - Node* caretNode = m_selection.start().deprecatedNode(); - LayoutRect oldRect = localCaretRectWithoutUpdate(); - LayoutRect newRect = localCaretRect(); + + RefPtr<Node> caretNode = m_previousCaretNode; + if (shouldUpdateCaretRect()) { + if (!isNonOrphanedCaret(m_selection)) + clearCaretRect(); + else { + VisiblePosition visibleStart = m_selection.visibleStart(); + if (updateCaretRect(m_frame->document(), visibleStart)) { + caretNode = visibleStart.deepEquivalent().deprecatedNode(); + m_absCaretBoundsDirty = true; + } + } + } + LayoutRect newRect = localCaretRectWithoutUpdate(); if (caretNode == m_previousCaretNode && oldRect == newRect && !m_absCaretBoundsDirty) return false; IntRect oldAbsCaretBounds = m_absCaretBounds; - m_absCaretBounds = absoluteBoundsForLocalRect(caretNode, localCaretRectWithoutUpdate()); + bool isInsideFixed; + m_absCaretBounds = absoluteBoundsForLocalCaretRect(rendererForCaretPainting(caretNode.get()), newRect, &isInsideFixed); + m_caretInsidePositionFixed = isInsideFixed; + + if (m_absCaretBoundsDirty && m_selection.isCaret()) // We should be able to always assert this condition. + ASSERT(m_absCaretBounds == m_selection.visibleStart().absoluteCaretBounds()); + m_absCaretBoundsDirty = false; if (caretNode == m_previousCaretNode && oldAbsCaretBounds == m_absCaretBounds) @@ -1430,12 +1642,12 @@ bool FrameSelection::recomputeCaretRect() #if ENABLE(TEXT_CARET) if (RenderView* view = m_frame->document()->renderView()) { - bool previousOrNewCaretNodeIsContentEditable = isContentEditable() || (m_previousCaretNode && m_previousCaretNode->isContentEditable()); + bool previousOrNewCaretNodeIsContentEditable = m_selection.isContentEditable() || (m_previousCaretNode && m_previousCaretNode->isContentEditable()); if (shouldRepaintCaret(view, previousOrNewCaretNodeIsContentEditable)) { if (m_previousCaretNode) repaintCaretForLocalRect(m_previousCaretNode.get(), oldRect); m_previousCaretNode = caretNode; - repaintCaretForLocalRect(caretNode, newRect); + repaintCaretForLocalRect(caretNode.get(), newRect); } } #endif @@ -1477,42 +1689,59 @@ void CaretBase::invalidateCaretRect(Node* node, bool caretRectChanged) return; if (RenderView* view = node->document().renderView()) { - if (shouldRepaintCaret(view, node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))) + if (shouldRepaintCaret(view, isEditableNode(*node))) repaintCaretForLocalRect(node, localCaretRectWithoutUpdate()); } } -void FrameSelection::paintCaret(GraphicsContext* context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) +void FrameSelection::paintCaret(GraphicsContext& context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) { if (m_selection.isCaret() && m_caretPaint) CaretBase::paintCaret(m_selection.start().deprecatedNode(), context, paintOffset, clipRect); } -void CaretBase::paintCaret(Node* node, GraphicsContext* context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const +#if ENABLE(TEXT_CARET) +static inline bool disappearsIntoBackground(const Color& foreground, const Color& background) +{ + return background.blend(foreground) == background; +} +#endif + +void CaretBase::paintCaret(Node* node, GraphicsContext& context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const { #if ENABLE(TEXT_CARET) if (m_caretVisibility == Hidden) return; LayoutRect drawingRect = localCaretRectWithoutUpdate(); - RenderObject* renderer = caretRenderer(node); - if (renderer && renderer->isBox()) - toRenderBox(renderer)->flipForWritingMode(drawingRect); + if (auto* renderer = rendererForCaretPainting(node)) + renderer->flipForWritingMode(drawingRect); drawingRect.moveBy(roundedIntPoint(paintOffset)); LayoutRect caret = intersection(drawingRect, clipRect); if (caret.isEmpty()) return; Color caretColor = Color::black; - ColorSpace colorSpace = ColorSpaceDeviceRGB; - Element* element = node->isElementNode() ? toElement(node) : node->parentElement(); + Element* element = is<Element>(*node) ? downcast<Element>(node) : node->parentElement(); + Element* rootEditableElement = node->rootEditableElement(); if (element && element->renderer()) { - caretColor = element->renderer()->style().visitedDependentColor(CSSPropertyColor); - colorSpace = element->renderer()->style().colorSpace(); + bool setToRootEditableElement = false; + if (rootEditableElement && rootEditableElement->renderer()) { + const auto& rootEditableStyle = rootEditableElement->renderer()->style(); + const auto& elementStyle = element->renderer()->style(); + auto rootEditableBGColor = rootEditableStyle.visitedDependentColor(CSSPropertyBackgroundColor); + auto elementBGColor = elementStyle.visitedDependentColor(CSSPropertyBackgroundColor); + if (disappearsIntoBackground(elementBGColor, rootEditableBGColor)) { + caretColor = rootEditableStyle.visitedDependentColor(CSSPropertyColor); + setToRootEditableElement = true; + } + } + if (!setToRootEditableElement) + caretColor = element->renderer()->style().visitedDependentColor(CSSPropertyColor); } - context->fillRect(caret, caretColor, colorSpace); + context.fillRect(caret, caretColor); #else UNUSED_PARAM(node); UNUSED_PARAM(context); @@ -1521,30 +1750,30 @@ void CaretBase::paintCaret(Node* node, GraphicsContext* context, const LayoutPoi #endif } -void FrameSelection::debugRenderer(RenderObject* r, bool selected) const +void FrameSelection::debugRenderer(RenderObject* renderer, bool selected) const { - if (r->node()->isElementNode()) { - Element* element = toElement(r->node()); - fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element->localName().string().utf8().data()); - } else if (r->isText()) { - RenderText* textRenderer = toRenderText(r); - if (!textRenderer->textLength() || !textRenderer->firstTextBox()) { + if (is<Element>(*renderer->node())) { + Element& element = downcast<Element>(*renderer->node()); + fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element.localName().string().utf8().data()); + } else if (is<RenderText>(*renderer)) { + RenderText& textRenderer = downcast<RenderText>(*renderer); + if (!textRenderer.textLength() || !textRenderer.firstTextBox()) { fprintf(stderr, "%s#text (empty)\n", selected ? "==> " : " "); return; } static const int max = 36; - String text = textRenderer->text(); + String text = textRenderer.text(); int textLength = text.length(); if (selected) { int offset = 0; - if (r->node() == m_selection.start().containerNode()) + if (renderer->node() == m_selection.start().containerNode()) offset = m_selection.start().computeOffsetInContainerNode(); - else if (r->node() == m_selection.end().containerNode()) + else if (renderer->node() == m_selection.end().containerNode()) offset = m_selection.end().computeOffsetInContainerNode(); int pos; - InlineTextBox* box = textRenderer->findNextInlineTextBox(offset, pos); + InlineTextBox* box = textRenderer.findNextInlineTextBox(offset, pos); text = text.substring(box->start(), box->len()); String show; @@ -1588,22 +1817,21 @@ void FrameSelection::debugRenderer(RenderObject* r, bool selected) const bool FrameSelection::contains(const LayoutPoint& point) { - Document* document = m_frame->document(); - // Treat a collapsed selection like no selection. if (!isRange()) return false; - if (!document->renderView()) + + RenderView* renderView = m_frame->contentRenderer(); + if (!renderView) return false; - HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowShadowContent); HitTestResult result(point); - document->renderView()->hitTest(request, result); + renderView->hitTest(HitTestRequest(), result); Node* innerNode = result.innerNode(); if (!innerNode || !innerNode->renderer()) return false; - VisiblePosition visiblePos(innerNode->renderer()->positionForPoint(result.localPoint())); + VisiblePosition visiblePos(innerNode->renderer()->positionForPoint(result.localPoint(), nullptr)); if (visiblePos.isNull()) return false; @@ -1653,7 +1881,7 @@ void FrameSelection::selectFrameElementInParentIfFullySelected() return; // Create compute positions before and after the element. - unsigned ownerElementNodeIndex = ownerElement->nodeIndex(); + unsigned ownerElementNodeIndex = ownerElement->computeNodeIndex(); VisiblePosition beforeOwnerElement(VisiblePosition(Position(ownerElementParent, ownerElementNodeIndex, Position::PositionIsOffsetInAnchor))); VisiblePosition afterOwnerElement(VisiblePosition(Position(ownerElementParent, ownerElementNodeIndex + 1, Position::PositionIsOffsetInAnchor), VP_UPSTREAM_IF_POSSIBLE)); @@ -1669,29 +1897,38 @@ void FrameSelection::selectAll() { Document* document = m_frame->document(); - if (document->focusedElement() && document->focusedElement()->hasTagName(selectTag)) { - HTMLSelectElement* selectElement = toHTMLSelectElement(document->focusedElement()); - if (selectElement->canSelectAll()) { - selectElement->selectAll(); + Element* focusedElement = document->focusedElement(); + if (is<HTMLSelectElement>(focusedElement)) { + HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*focusedElement); + if (selectElement.canSelectAll()) { + selectElement.selectAll(); return; } } - RefPtr<Node> root = 0; - Node* selectStartTarget = 0; - if (isContentEditable()) { + RefPtr<Node> root; + Node* selectStartTarget = nullptr; + if (m_selection.isContentEditable()) { root = highestEditableRoot(m_selection.start()); if (Node* shadowRoot = m_selection.nonBoundaryShadowTreeRootNode()) selectStartTarget = shadowRoot->shadowHost(); else selectStartTarget = root.get(); } else { - root = m_selection.nonBoundaryShadowTreeRootNode(); + if (m_selection.isNone() && focusedElement) { + if (is<HTMLTextFormControlElement>(*focusedElement)) { + downcast<HTMLTextFormControlElement>(*focusedElement).select(); + return; + } + root = focusedElement->nonBoundaryShadowTreeRootNode(); + } else + root = m_selection.nonBoundaryShadowTreeRootNode(); + if (root) selectStartTarget = root->shadowHost(); else { root = document->documentElement(); - selectStartTarget = document->body(); + selectStartTarget = document->bodyOrFrameset(); } } if (!root) @@ -1702,45 +1939,36 @@ void FrameSelection::selectAll() VisibleSelection newSelection(VisibleSelection::selectionFromContentsOfNode(root.get())); - if (shouldChangeSelection(newSelection)) - setSelection(newSelection); - - selectFrameElementInParentIfFullySelected(); - updateSelectionCachesIfSelectionIsInsideTextFormControl(UserTriggered); + if (shouldChangeSelection(newSelection)) { + AXTextStateChangeIntent intent(AXTextStateChangeTypeSelectionExtend, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityAll, false }); + setSelection(newSelection, defaultSetSelectionOptions() | FireSelectEvent, intent); + } } -bool FrameSelection::setSelectedRange(Range* range, EAffinity affinity, bool closeTyping) +bool FrameSelection::setSelectedRange(Range* range, EAffinity affinity, bool closeTyping, EUserTriggered userTriggered) { - if (!range || !range->startContainer() || !range->endContainer()) + if (!range) return false; - ASSERT(&range->startContainer()->document() == &range->endContainer()->document()); + ASSERT(&range->startContainer().document() == &range->endContainer().document()); - m_frame->document()->updateLayoutIgnorePendingStylesheets(); - - // Non-collapsed ranges are not allowed to start at the end of a line that is wrapped, - // they start at the beginning of the next line instead - ExceptionCode ec = 0; - bool collapsed = range->collapsed(ec); - if (ec) - return false; + VisibleSelection newSelection(*range, affinity); - // FIXME: Can we provide extentAffinity? - VisiblePosition visibleStart(range->startPosition(), collapsed ? affinity : DOWNSTREAM); - VisiblePosition visibleEnd(range->endPosition(), SEL_DEFAULT_AFFINITY); #if PLATFORM(IOS) - if (range->startContainer() && visibleStart.isNull()) - return false; - if (range->endContainer() && visibleEnd.isNull()) + // FIXME: Why do we need this check only in iOS? + if (newSelection.isNone()) return false; #endif - setSelection(VisibleSelection(visibleStart, visibleEnd), ClearTypingStyle | (closeTyping ? CloseTyping : 0)); - return true; -} -bool FrameSelection::isInPasswordField() const -{ - HTMLTextFormControlElement* textControl = enclosingTextFormControl(start()); - return textControl && isHTMLInputElement(textControl) && toHTMLInputElement(textControl)->isPasswordField(); + if (userTriggered == UserTriggered) { + FrameSelection trialFrameSelection; + trialFrameSelection.setSelection(newSelection, ClearTypingStyle | (closeTyping ? CloseTyping : 0)); + + if (!shouldChangeSelection(trialFrameSelection.selection())) + return false; + } + + setSelection(newSelection, ClearTypingStyle | (closeTyping ? CloseTyping : 0)); + return true; } void FrameSelection::focusedOrActiveStateChanged() @@ -1767,17 +1995,14 @@ void FrameSelection::focusedOrActiveStateChanged() setSelectionFromNone(); setCaretVisibility(activeAndFocused ? Visible : Hidden); - // Update for caps lock state - m_frame->eventHandler().capsLockStateMayHaveChanged(); - // 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 (Element* element = document->focusedElement()) { - element->setNeedsStyleRecalc(); + element->invalidateStyleForSubtree(); if (RenderObject* renderer = element->renderer()) if (renderer && renderer->style().hasAppearance()) - renderer->theme().stateChanged(renderer, FocusState); + renderer->theme().stateChanged(*renderer, ControlStates::FocusState); } #endif } @@ -1817,17 +2042,14 @@ void FrameSelection::updateAppearance() // Paint a block cursor instead of a caret in overtype mode unless the caret is at the end of a line (in this case // the FrameSelection will paint a blinking caret as usual). - VisiblePosition forwardPosition; - if (m_shouldShowBlockCursor && m_selection.isCaret()) { - forwardPosition = modifyExtendingForward(CharacterGranularity); - m_caretPaint = forwardPosition.isNull(); - } + VisibleSelection oldSelection = selection(); #if ENABLE(TEXT_CARET) + bool paintBlockCursor = m_shouldShowBlockCursor && m_selection.isCaret() && !isLogicalEndOfLine(m_selection.visibleEnd()); bool caretRectChangedOrCleared = recomputeCaretRect(); bool caretBrowsing = m_frame->settings().caretBrowsingEnabled(); - bool shouldBlink = caretIsVisible() && isCaret() && (isContentEditable() || caretBrowsing) && forwardPosition.isNull(); + bool shouldBlink = !paintBlockCursor && caretIsVisible() && isCaret() && (oldSelection.isContentEditable() || caretBrowsing); // If the caret moved, stop the blink timer so we can restart with a // black caret in the new location. @@ -1853,7 +2075,12 @@ void FrameSelection::updateAppearance() // Construct a new VisibleSolution, since m_selection is not necessarily valid, and the following steps // assume a valid selection. See <https://bugs.webkit.org/show_bug.cgi?id=69563> and <rdar://problem/10232866>. - VisibleSelection selection(m_selection.visibleStart(), forwardPosition.isNotNull() ? forwardPosition : m_selection.visibleEnd()); +#if ENABLE(TEXT_CARET) + VisiblePosition endVisiblePosition = paintBlockCursor ? modifyExtendingForward(CharacterGranularity) : oldSelection.visibleEnd(); + VisibleSelection selection(oldSelection.visibleStart(), endVisiblePosition); +#else + VisibleSelection selection(oldSelection.visibleStart(), oldSelection.visibleEnd()); +#endif if (!selection.isRange()) { view->clearSelection(); @@ -1877,8 +2104,11 @@ void FrameSelection::updateAppearance() // because we don't yet notify the FrameSelection of text removal. if (startPos.isNotNull() && endPos.isNotNull() && selection.visibleStart() != selection.visibleEnd()) { RenderObject* startRenderer = startPos.deprecatedNode()->renderer(); + int startOffset = startPos.deprecatedEditingOffset(); RenderObject* endRenderer = endPos.deprecatedNode()->renderer(); - view->setSelection(startRenderer, startPos.deprecatedEditingOffset(), endRenderer, endPos.deprecatedEditingOffset()); + int endOffset = endPos.deprecatedEditingOffset(); + ASSERT(startOffset >= 0 && endOffset >= 0); + view->setSelection(startRenderer, startOffset, endRenderer, endOffset); } } @@ -1887,21 +2117,22 @@ void FrameSelection::setCaretVisibility(CaretVisibility visibility) if (caretVisibility() == visibility) return; + // FIXME: We shouldn't trigger a synchronous layout here. + if (m_frame) + updateSelectionByUpdatingLayoutOrStyle(*m_frame); + #if ENABLE(TEXT_CARET) - m_frame->document()->updateLayoutIgnorePendingStylesheets(); if (m_caretPaint) { m_caretPaint = false; invalidateCaretRect(); } CaretBase::setCaretVisibility(visibility); -#else - m_frame->document()->updateStyleIfNeeded(); #endif updateAppearance(); } -void FrameSelection::caretBlinkTimerFired(Timer<FrameSelection>&) +void FrameSelection::caretBlinkTimerFired() { #if ENABLE(TEXT_CARET) ASSERT(caretIsVisible()); @@ -1914,12 +2145,6 @@ void FrameSelection::caretBlinkTimerFired(Timer<FrameSelection>&) #endif } -void FrameSelection::updateSelectionCachesIfSelectionIsInsideTextFormControl(EUserTriggered userTriggered) -{ - if (HTMLTextFormControlElement* textControl = enclosingTextFormControl(start())) - textControl->selectionChanged(userTriggered == UserTriggered); -} - // Helper function that tells whether a particular node is an element that has an entire // Frame and FrameView, a <frame>, <iframe>, or <object>. static bool isFrameElement(const Node* n) @@ -1927,9 +2152,9 @@ static bool isFrameElement(const Node* n) if (!n) return false; RenderObject* renderer = n->renderer(); - if (!renderer || !renderer->isWidget()) + if (!is<RenderWidget>(renderer)) return false; - Widget* widget = toRenderWidget(renderer)->widget(); + Widget* widget = downcast<RenderWidget>(*renderer).widget(); return widget && widget->isFrameView(); } @@ -1940,32 +2165,32 @@ void FrameSelection::setFocusedElementIfNeeded() bool caretBrowsing = m_frame->settings().caretBrowsingEnabled(); if (caretBrowsing) { - if (Element* anchor = enclosingAnchorElement(base())) { - m_frame->page()->focusController().setFocusedElement(anchor, m_frame); + if (Element* anchor = enclosingAnchorElement(m_selection.base())) { + m_frame->page()->focusController().setFocusedElement(anchor, *m_frame); return; } } - if (Element* target = rootEditableElement()) { + if (Element* target = m_selection.rootEditableElement()) { // Walk up the DOM tree to search for an element to focus. while (target) { // We don't want to set focus on a subframe when selecting in a parent frame, // so add the !isFrameElement check here. There's probably a better way to make this // work in the long term, but this is the safest fix at this time. if (target->isMouseFocusable() && !isFrameElement(target)) { - m_frame->page()->focusController().setFocusedElement(target, m_frame); + m_frame->page()->focusController().setFocusedElement(target, *m_frame); return; } target = target->parentOrShadowHostElement(); } - m_frame->document()->setFocusedElement(0); + m_frame->document()->setFocusedElement(nullptr); } if (caretBrowsing) - m_frame->page()->focusController().setFocusedElement(0, m_frame); + m_frame->page()->focusController().setFocusedElement(nullptr, *m_frame); } -void DragCaretController::paintDragCaret(Frame* frame, GraphicsContext* p, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const +void DragCaretController::paintDragCaret(Frame* frame, GraphicsContext& p, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const { #if ENABLE(TEXT_CARET) if (m_position.deepEquivalent().deprecatedNode()->document().frame() == frame) @@ -1994,12 +2219,12 @@ bool FrameSelection::shouldDeleteSelection(const VisibleSelection& selection) co return m_frame->editor().client()->shouldDeleteRange(selection.toNormalizedRange().get()); } -FloatRect FrameSelection::bounds(bool clipToVisibleContent) const +FloatRect FrameSelection::selectionBounds(bool clipToVisibleContent) const { if (!m_frame->document()) return LayoutRect(); - m_frame->document()->updateStyleIfNeeded(); + updateSelectionByUpdatingLayoutOrStyle(*m_frame); RenderView* root = m_frame->contentRenderer(); FrameView* view = m_frame->view(); if (!root || !view) @@ -2009,25 +2234,37 @@ FloatRect FrameSelection::bounds(bool clipToVisibleContent) const return clipToVisibleContent ? intersection(selectionRect, view->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect)) : selectionRect; } -void FrameSelection::getClippedVisibleTextRectangles(Vector<FloatRect>& rectangles) const +void FrameSelection::getClippedVisibleTextRectangles(Vector<FloatRect>& rectangles, TextRectangleHeight textRectHeight) const { RenderView* root = m_frame->contentRenderer(); if (!root) return; - FloatRect visibleContentRect = m_frame->view()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); + Vector<FloatRect> textRects; + getTextRectangles(textRects, textRectHeight); - Vector<FloatQuad> quads; - toNormalizedRange()->textQuads(quads, true); + FloatRect visibleContentRect = m_frame->view()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); - size_t size = quads.size(); - for (size_t i = 0; i < size; ++i) { - FloatRect intersectionRect = intersection(quads[i].enclosingBoundingBox(), visibleContentRect); + for (const auto& rect : textRects) { + FloatRect intersectionRect = intersection(rect, visibleContentRect); if (!intersectionRect.isEmpty()) rectangles.append(intersectionRect); } } +void FrameSelection::getTextRectangles(Vector<FloatRect>& rectangles, TextRectangleHeight textRectHeight) const +{ + RefPtr<Range> range = toNormalizedRange(); + if (!range) + return; + + Vector<FloatQuad> quads; + range->absoluteTextQuads(quads, textRectHeight == TextRectangleHeight::SelectionHeight); + + for (const auto& quad : quads) + rectangles.append(quad.boundingBox()); +} + // Scans logically forward from "start", including any child frames. static HTMLFormElement* scanForForm(Element* start) { @@ -2037,12 +2274,12 @@ static HTMLFormElement* scanForForm(Element* start) auto descendants = descendantsOfType<HTMLElement>(start->document()); for (auto it = descendants.from(*start), end = descendants.end(); it != end; ++it) { HTMLElement& element = *it; - if (isHTMLFormElement(&element)) - return toHTMLFormElement(&element); - if (isHTMLFormControlElement(element)) - return toHTMLFormControlElement(element).form(); - if (isHTMLFrameElementBase(element)) { - Document* contentDocument = toHTMLFrameElementBase(element).contentDocument(); + if (is<HTMLFormElement>(element)) + return &downcast<HTMLFormElement>(element); + if (is<HTMLFormControlElement>(element)) + return downcast<HTMLFormControlElement>(element).form(); + if (is<HTMLFrameElementBase>(element)) { + Document* contentDocument = downcast<HTMLFrameElementBase>(element).contentDocument(); if (!contentDocument) continue; if (HTMLFormElement* frameResult = scanForForm(contentDocument->documentElement())) @@ -2058,7 +2295,7 @@ HTMLFormElement* FrameSelection::currentForm() const // Start looking either at the active (first responder) node, or where the selection is. Element* start = m_frame->document()->focusedElement(); if (!start) - start = this->start().element(); + start = m_selection.start().element(); if (!start) return nullptr; @@ -2071,40 +2308,43 @@ HTMLFormElement* FrameSelection::currentForm() const return scanForForm(start); } -void FrameSelection::revealSelection(const ScrollAlignment& alignment, RevealExtentOption revealExtentOption) +void FrameSelection::revealSelection(SelectionRevealMode revealMode, const ScrollAlignment& alignment, RevealExtentOption revealExtentOption) { - LayoutRect rect; + if (revealMode == SelectionRevealMode::DoNotReveal) + return; - switch (selectionType()) { + LayoutRect rect; + bool insideFixed = false; + switch (m_selection.selectionType()) { case VisibleSelection::NoSelection: return; case VisibleSelection::CaretSelection: - rect = absoluteCaretBounds(); + rect = absoluteCaretBounds(&insideFixed); break; case VisibleSelection::RangeSelection: - rect = revealExtentOption == RevealExtent ? VisiblePosition(extent()).absoluteCaretBounds() : enclosingIntRect(bounds(false)); + rect = revealExtentOption == RevealExtent ? VisiblePosition(m_selection.extent()).absoluteCaretBounds() : enclosingIntRect(selectionBounds(false)); break; } - Position start = this->start(); + Position start = m_selection.start(); ASSERT(start.deprecatedNode()); if (start.deprecatedNode() && start.deprecatedNode()->renderer()) { #if PLATFORM(IOS) if (RenderLayer* layer = start.deprecatedNode()->renderer()->enclosingLayer()) { if (!m_scrollingSuppressCount) { layer->setAdjustForIOSCaretWhenScrolling(true); - layer->scrollRectToVisible(rect, alignment, alignment); + layer->scrollRectToVisible(revealMode, rect, insideFixed, alignment, alignment); layer->setAdjustForIOSCaretWhenScrolling(false); updateAppearance(); if (m_frame->page()) - m_frame->page()->chrome().client().notifyRevealedSelectionByScrollingFrame(m_frame); + m_frame->page()->chrome().client().notifyRevealedSelectionByScrollingFrame(*m_frame); } } #else // 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 (start.deprecatedNode()->renderer()->scrollRectToVisible(rect, alignment, alignment)) + if (start.deprecatedNode()->renderer()->scrollRectToVisible(revealMode, rect, insideFixed, alignment, alignment)) updateAppearance(); #endif } @@ -2121,15 +2361,12 @@ void FrameSelection::setSelectionFromNone() if (!isNone() || !(document->hasEditableStyle() || caretBrowsing)) return; #else - if (!document || !(isNone() || isStartOfDocument(VisiblePosition(selection().start(), selection().affinity()))) || !document->hasEditableStyle()) + if (!document || !(isNone() || isStartOfDocument(VisiblePosition(m_selection.start(), m_selection.affinity()))) || !document->hasEditableStyle()) return; #endif - Node* node = document->documentElement(); - while (node && !node->hasTagName(bodyTag)) - node = NodeTraversal::next(node); - if (node) - setSelection(VisibleSelection(firstPositionInOrBeforeNode(node), DOWNSTREAM)); + if (auto* body = document->body()) + setSelection(VisibleSelection(firstPositionInOrBeforeNode(body), DOWNSTREAM)); } bool FrameSelection::shouldChangeSelection(const VisibleSelection& newSelection) const @@ -2159,7 +2396,33 @@ void FrameSelection::setShouldShowBlockCursor(bool shouldShowBlockCursor) updateAppearance(); } -#ifndef NDEBUG +void FrameSelection::updateAppearanceAfterLayout() +{ + m_appearanceUpdateTimer.stop(); + updateAppearanceAfterLayoutOrStyleChange(); +} + +void FrameSelection::scheduleAppearanceUpdateAfterStyleChange() +{ + m_appearanceUpdateTimer.startOneShot(0_s); +} + +void FrameSelection::appearanceUpdateTimerFired() +{ + updateAppearanceAfterLayoutOrStyleChange(); +} + +void FrameSelection::updateAppearanceAfterLayoutOrStyleChange() +{ + if (auto* client = m_frame->editor().client()) + client->updateEditorStateAfterLayoutIfEditabilityChanged(); + + setCaretRectNeedsUpdate(); + updateAndRevealSelection(AXTextStateChangeIntent()); + updateDataDetectorsForSelection(); +} + +#if ENABLE(TREE_DEBUGGING) void FrameSelection::formatForDebugger(char* buffer, unsigned length) const { @@ -2179,7 +2442,7 @@ void FrameSelection::expandSelectionToElementContainingCaretSelection() RefPtr<Range> range = elementRangeContainingCaretSelection(); if (!range) return; - VisibleSelection selection(range.get(), DOWNSTREAM); + VisibleSelection selection(*range, DOWNSTREAM); setSelection(selection); } @@ -2202,7 +2465,7 @@ PassRefPtr<Range> FrameSelection::elementRangeContainingCaretSelection() const return nullptr; Position startPos = createLegacyEditingPosition(element, 0); - Position endPos = createLegacyEditingPosition(element, element->childNodeCount()); + Position endPos = createLegacyEditingPosition(element, element->countChildNodes()); VisiblePosition startVisiblePos(startPos, VP_DEFAULT_AFFINITY); VisiblePosition endVisiblePos(endPos, VP_DEFAULT_AFFINITY); @@ -2302,9 +2565,7 @@ int FrameSelection::wordOffsetInRange(const Range *range) const // FIXME: This will only work in cases where the selection remains in // the same node after it is expanded. Improve to handle more complicated // cases. - ExceptionCode ec = 0; - int result = selection.start().deprecatedEditingOffset() - range->startOffset(ec); - ASSERT(!ec); + int result = selection.start().deprecatedEditingOffset() - range->startOffset(); if (result < 0) result = 0; return result; @@ -2314,12 +2575,9 @@ bool FrameSelection::spaceFollowsWordInRange(const Range *range) const { if (!range) return false; - ExceptionCode ec = 0; - Node* node = range->endContainer(ec); - ASSERT(!ec); - int endOffset = range->endOffset(ec); - ASSERT(!ec); - VisiblePosition pos(createLegacyEditingPosition(node, endOffset), VP_DEFAULT_AFFINITY); + Node& node = range->endContainer(); + int endOffset = range->endOffset(); + VisiblePosition pos(createLegacyEditingPosition(&node, endOffset), VP_DEFAULT_AFFINITY); return isSpaceOrNewline(pos.characterAfter()); } @@ -2389,15 +2647,12 @@ PassRefPtr<Range> FrameSelection::rangeByExtendingCurrentSelection(int amount) c return rangeByAlteringCurrentSelection(AlterationExtend, amount); } -void FrameSelection::selectRangeOnElement(unsigned location, unsigned length, Node* node) +void FrameSelection::selectRangeOnElement(unsigned location, unsigned length, Node& node) { RefPtr<Range> resultRange = m_frame->document()->createRange(); - ExceptionCode ec = 0; - resultRange->setStart(node, location, ec); - ASSERT(!ec); - resultRange->setEnd(node, location + length, ec); - ASSERT(!ec); - VisibleSelection selection = VisibleSelection(resultRange.get(), SEL_DEFAULT_AFFINITY); + resultRange->setStart(node, location); + resultRange->setEnd(node, location + length); + VisibleSelection selection = VisibleSelection(*resultRange, SEL_DEFAULT_AFFINITY); setSelection(selection, true); } @@ -2433,7 +2688,7 @@ VisibleSelection FrameSelection::wordSelectionContainingCaretSelection(const Vis VisibleSelection newSelection = frameSelection.selection(); newSelection.expandUsingGranularity(WordGranularity); - frameSelection.setSelection(newSelection, frameSelection.granularity()); + frameSelection.setSelection(newSelection, defaultSetSelectionOptions(), AXTextStateChangeIntent(), AlignCursorOnScrollIfNeeded, frameSelection.granularity()); Position startPos(frameSelection.selection().start()); Position endPos(frameSelection.selection().end()); @@ -2598,7 +2853,7 @@ void FrameSelection::setCaretColor(const Color& caretColor) } -#ifndef NDEBUG +#if ENABLE(TREE_DEBUGGING) void showTree(const WebCore::FrameSelection& sel) { diff --git a/Source/WebCore/editing/FrameSelection.h b/Source/WebCore/editing/FrameSelection.h index 2962d64f4..c1ea10168 100644 --- a/Source/WebCore/editing/FrameSelection.h +++ b/Source/WebCore/editing/FrameSelection.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,14 +23,15 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef FrameSelection_h -#define FrameSelection_h +#pragma once +#include "AXTextStateChangeIntent.h" #include "EditingStyle.h" +#include "Element.h" #include "IntRect.h" #include "LayoutRect.h" #include "Range.h" -#include "ScrollBehavior.h" +#include "ScrollAlignment.h" #include "Timer.h" #include "VisibleSelection.h" #include <wtf/Noncopyable.h> @@ -46,9 +47,9 @@ class Frame; class GraphicsContext; class HTMLFormElement; class MutableStyleProperties; +class RenderBlock; class RenderObject; class RenderView; -class Settings; class VisiblePosition; enum EUserTriggered { NotUserTriggered = 0, UserTriggered = 1 }; @@ -68,9 +69,8 @@ protected: void invalidateCaretRect(Node*, bool caretRectChanged = false); void clearCaretRect(); bool updateCaretRect(Document*, const VisiblePosition& caretPosition); - IntRect absoluteBoundsForLocalRect(Node*, const LayoutRect&) const; bool shouldRepaintCaret(const RenderView*, bool isContentEditable) const; - void paintCaret(Node*, GraphicsContext*, const LayoutPoint&, const LayoutRect& clipRect) const; + void paintCaret(Node*, GraphicsContext&, const LayoutPoint&, const LayoutRect& clipRect) const; const LayoutRect& localCaretRectWithoutUpdate() const { return m_caretLocalRect; } @@ -93,8 +93,8 @@ class DragCaretController : private CaretBase { public: DragCaretController(); - RenderObject* caretRenderer() const; - void paintDragCaret(Frame*, GraphicsContext*, const LayoutPoint&, const LayoutRect& clipRect) const; + RenderBlock* caretRenderer() const; + void paintDragCaret(Frame*, GraphicsContext&, const LayoutPoint&, const LayoutRect& clipRect) const; bool isContentEditable() const { return m_position.rootEditableElement(); } bool isContentRichlyEditable() const; @@ -104,7 +104,7 @@ public: void setCaretPosition(const VisiblePosition&); void clear() { setCaretPosition(VisiblePosition()); } - void nodeWillBeRemoved(Node*); + void nodeWillBeRemoved(Node&); private: VisiblePosition m_position; @@ -118,53 +118,45 @@ public: enum CursorAlignOnScroll { AlignCursorOnScrollIfNeeded, AlignCursorOnScrollAlways }; enum SetSelectionOption { - // 1 << 0 is reserved for EUserTriggered + FireSelectEvent = 1 << 0, CloseTyping = 1 << 1, ClearTypingStyle = 1 << 2, SpellCorrectionTriggered = 1 << 3, DoNotSetFocus = 1 << 4, DictationTriggered = 1 << 5, - DoNotUpdateAppearance = 1 << 6, + RevealSelection = 1 << 6, }; typedef unsigned SetSelectionOptions; // Union of values in SetSelectionOption and EUserTriggered - static inline EUserTriggered selectionOptionsToUserTriggered(SetSelectionOptions options) + static inline SetSelectionOptions defaultSetSelectionOptions(EUserTriggered userTriggered = NotUserTriggered) { - return static_cast<EUserTriggered>(options & UserTriggered); + return CloseTyping | ClearTypingStyle | (userTriggered ? (RevealSelection | FireSelectEvent) : 0); } - explicit FrameSelection(Frame* = 0); + WEBCORE_EXPORT explicit FrameSelection(Frame* = nullptr); - Element* rootEditableElement() const { return m_selection.rootEditableElement(); } - Element* rootEditableElementOrDocumentElement() const; - - bool hasEditableStyle() const { return m_selection.hasEditableStyle(); } - bool isContentEditable() const { return m_selection.isContentEditable(); } - bool isContentRichlyEditable() const { return m_selection.isContentRichlyEditable(); } + WEBCORE_EXPORT Element* rootEditableElementOrDocumentElement() const; - void moveTo(const Range*, EAffinity, EUserTriggered = NotUserTriggered); - void moveTo(const VisiblePosition&, EUserTriggered = NotUserTriggered, CursorAlignOnScroll = AlignCursorOnScrollIfNeeded); - void moveTo(const VisiblePosition&, const VisiblePosition&, EUserTriggered = NotUserTriggered); + WEBCORE_EXPORT void moveTo(const Range*); + WEBCORE_EXPORT void moveTo(const VisiblePosition&, EUserTriggered = NotUserTriggered, CursorAlignOnScroll = AlignCursorOnScrollIfNeeded); + WEBCORE_EXPORT void moveTo(const VisiblePosition&, const VisiblePosition&, EUserTriggered = NotUserTriggered); void moveTo(const Position&, EAffinity, EUserTriggered = NotUserTriggered); void moveTo(const Position&, const Position&, EAffinity, EUserTriggered = NotUserTriggered); + void moveWithoutValidationTo(const Position&, const Position&, bool selectionHasDirection, bool shouldSetFocus, const AXTextStateChangeIntent& = AXTextStateChangeIntent()); const VisibleSelection& selection() const { return m_selection; } - void setSelection(const VisibleSelection&, SetSelectionOptions = CloseTyping | ClearTypingStyle, CursorAlignOnScroll = AlignCursorOnScrollIfNeeded, TextGranularity = CharacterGranularity); - void setSelection(const VisibleSelection& selection, TextGranularity granularity) { setSelection(selection, CloseTyping | ClearTypingStyle, AlignCursorOnScrollIfNeeded, granularity); } - bool setSelectedRange(Range*, EAffinity, bool closeTyping); - void selectAll(); - void clear(); + WEBCORE_EXPORT void setSelection(const VisibleSelection&, SetSelectionOptions = defaultSetSelectionOptions(), AXTextStateChangeIntent = AXTextStateChangeIntent(), CursorAlignOnScroll = AlignCursorOnScrollIfNeeded, TextGranularity = CharacterGranularity); + WEBCORE_EXPORT bool setSelectedRange(Range*, EAffinity, bool closeTyping, EUserTriggered = NotUserTriggered); + WEBCORE_EXPORT void selectAll(); + WEBCORE_EXPORT void clear(); void prepareForDestruction(); - // Call this after doing user-triggered selections to make it easy to delete the frame you entirely selected. - void selectFrameElementInParentIfFullySelected(); + void updateAppearanceAfterLayout(); + void scheduleAppearanceUpdateAfterStyleChange(); + void setNeedsSelectionUpdate(); bool contains(const LayoutPoint&); - VisibleSelection::SelectionType selectionType() const { return m_selection.selectionType(); } - - EAffinity affinity() const { return m_selection.affinity(); } - - bool modify(EAlteration, SelectionDirection, TextGranularity, EUserTriggered = NotUserTriggered); + WEBCORE_EXPORT bool modify(EAlteration, SelectionDirection, TextGranularity, EUserTriggered = NotUserTriggered); enum VerticalDirection { DirectionUp, DirectionDown }; bool modify(EAlteration, unsigned verticalDistance, VerticalDirection, EUserTriggered = NotUserTriggered, CursorAlignOnScroll = AlignCursorOnScrollIfNeeded); @@ -178,19 +170,11 @@ public: void setExtent(const VisiblePosition&, EUserTriggered = NotUserTriggered); void setExtent(const Position&, EAffinity, EUserTriggered = NotUserTriggered); - Position base() const { return m_selection.base(); } - Position extent() const { return m_selection.extent(); } - Position start() const { return m_selection.start(); } - Position end() const { return m_selection.end(); } - // Return the renderer that is responsible for painting the caret (in the selection start node) - RenderObject* caretRenderer() const; - - // Caret rect local to the caret's renderer - LayoutRect localCaretRect(); + RenderBlock* caretRendererWithoutUpdatingLayout() const; // Bounds of (possibly transformed) caret in absolute coords - IntRect absoluteCaretBounds(); + WEBCORE_EXPORT IntRect absoluteCaretBounds(bool* insideFixed = nullptr); void setCaretRectNeedsUpdate() { CaretBase::setCaretRectNeedsUpdate(); } void willBeModified(EAlteration, SelectionDirection); @@ -199,20 +183,17 @@ public: bool isCaret() const { return m_selection.isCaret(); } bool isRange() const { return m_selection.isRange(); } bool isCaretOrRange() const { return m_selection.isCaretOrRange(); } - bool isInPasswordField() const; bool isAll(EditingBoundaryCrossingRule rule = CannotCrossEditingBoundary) const { return m_selection.isAll(rule); } - PassRefPtr<Range> toNormalizedRange() const { return m_selection.toNormalizedRange(); } + RefPtr<Range> toNormalizedRange() const { return m_selection.toNormalizedRange(); } void debugRenderer(RenderObject*, bool selected) const; - void nodeWillBeRemoved(Node*); + void nodeWillBeRemoved(Node&); void textWasReplaced(CharacterData*, unsigned offset, unsigned oldLength, unsigned newLength); void setCaretVisible(bool caretIsVisible) { setCaretVisibility(caretIsVisible ? Visible : Hidden); } - bool recomputeCaretRect(); - void invalidateCaretRect(); - void paintCaret(GraphicsContext*, const LayoutPoint&, const LayoutRect& clipRect); + void paintCaret(GraphicsContext&, const LayoutPoint&, const LayoutRect& clipRect); // Used to suspend caret blinking while the mouse is down. void setCaretBlinkingSuspended(bool suspended) { m_isCaretBlinkingSuspended = suspended; } @@ -221,41 +202,39 @@ public: // Focus void setFocused(bool); bool isFocused() const { return m_focused; } - bool isFocusedAndActive() const; + WEBCORE_EXPORT bool isFocusedAndActive() const; void pageActivationChanged(); // Painting. - void updateAppearance(); + WEBCORE_EXPORT void updateAppearance(); -#ifndef NDEBUG +#if ENABLE(TREE_DEBUGGING) void formatForDebugger(char* buffer, unsigned length) const; void showTreeForThis() const; #endif #if PLATFORM(IOS) public: - void expandSelectionToElementContainingCaretSelection(); - PassRefPtr<Range> elementRangeContainingCaretSelection() const; - void expandSelectionToWordContainingCaretSelection(); - PassRefPtr<Range> wordRangeContainingCaretSelection(); - void expandSelectionToStartOfWordContainingCaretSelection(); - UChar characterInRelationToCaretSelection(int amount) const; - UChar characterBeforeCaretSelection() const; - UChar characterAfterCaretSelection() const; - int wordOffsetInRange(const Range*) const; - bool spaceFollowsWordInRange(const Range*) const; - bool selectionAtDocumentStart() const; - bool selectionAtSentenceStart() const; - bool selectionAtWordStart() const; - PassRefPtr<Range> rangeByMovingCurrentSelection(int amount) const; - PassRefPtr<Range> rangeByExtendingCurrentSelection(int amount) const; - void selectRangeOnElement(unsigned location, unsigned length, Node*); - void suppressCloseTyping() { ++m_closeTypingSuppressions; } - void restoreCloseTyping() { --m_closeTypingSuppressions; } - void clearCurrentSelection(); + WEBCORE_EXPORT void expandSelectionToElementContainingCaretSelection(); + WEBCORE_EXPORT PassRefPtr<Range> elementRangeContainingCaretSelection() const; + WEBCORE_EXPORT void expandSelectionToWordContainingCaretSelection(); + WEBCORE_EXPORT PassRefPtr<Range> wordRangeContainingCaretSelection(); + WEBCORE_EXPORT void expandSelectionToStartOfWordContainingCaretSelection(); + WEBCORE_EXPORT UChar characterInRelationToCaretSelection(int amount) const; + WEBCORE_EXPORT UChar characterBeforeCaretSelection() const; + WEBCORE_EXPORT UChar characterAfterCaretSelection() const; + WEBCORE_EXPORT int wordOffsetInRange(const Range*) const; + WEBCORE_EXPORT bool spaceFollowsWordInRange(const Range*) const; + WEBCORE_EXPORT bool selectionAtDocumentStart() const; + WEBCORE_EXPORT bool selectionAtSentenceStart() const; + WEBCORE_EXPORT bool selectionAtWordStart() const; + WEBCORE_EXPORT PassRefPtr<Range> rangeByMovingCurrentSelection(int amount) const; + WEBCORE_EXPORT PassRefPtr<Range> rangeByExtendingCurrentSelection(int amount) const; + WEBCORE_EXPORT void selectRangeOnElement(unsigned location, unsigned length, Node&); + WEBCORE_EXPORT void clearCurrentSelection(); void setCaretBlinks(bool caretBlinks = true); - void setCaretColor(const Color&); - static VisibleSelection wordSelectionContainingCaretSelection(const VisibleSelection&); + WEBCORE_EXPORT void setCaretColor(const Color&); + WEBCORE_EXPORT static VisibleSelection wordSelectionContainingCaretSelection(const VisibleSelection&); void setUpdateAppearanceEnabled(bool enabled) { m_updateAppearanceEnabled = enabled; } void suppressScrolling() { ++m_scrollingSuppressCount; } void restoreScrolling() @@ -272,24 +251,23 @@ public: bool shouldChangeSelection(const VisibleSelection&) const; bool shouldDeleteSelection(const VisibleSelection&) const; enum EndPointsAdjustmentMode { AdjustEndpointsAtBidiBoundary, DoNotAdjsutEndpoints }; - void setNonDirectionalSelectionIfNeeded(const VisibleSelection&, TextGranularity, EndPointsAdjustmentMode = DoNotAdjsutEndpoints); - void updateSelectionCachesIfSelectionIsInsideTextFormControl(EUserTriggered); - - void paintDragCaret(GraphicsContext*, const LayoutPoint&, const LayoutRect& clipRect) const; + void setSelectionByMouseIfDifferent(const VisibleSelection&, TextGranularity, EndPointsAdjustmentMode = DoNotAdjsutEndpoints); EditingStyle* typingStyle() const; - PassRefPtr<MutableStyleProperties> copyTypingStyle() const; + WEBCORE_EXPORT PassRefPtr<MutableStyleProperties> copyTypingStyle() const; void setTypingStyle(PassRefPtr<EditingStyle>); void clearTypingStyle(); - FloatRect bounds(bool clipToVisibleContent = true) const; + WEBCORE_EXPORT FloatRect selectionBounds(bool clipToVisibleContent = true) const; - void getClippedVisibleTextRectangles(Vector<FloatRect>&) const; + enum class TextRectangleHeight { TextHeight, SelectionHeight }; + WEBCORE_EXPORT void getClippedVisibleTextRectangles(Vector<FloatRect>&, TextRectangleHeight = TextRectangleHeight::SelectionHeight) const; + WEBCORE_EXPORT void getTextRectangles(Vector<FloatRect>&, TextRectangleHeight = TextRectangleHeight::SelectionHeight) const; - HTMLFormElement* currentForm() const; + WEBCORE_EXPORT HTMLFormElement* currentForm() const; - void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, RevealExtentOption = DoNotRevealExtent); - void setSelectionFromNone(); + WEBCORE_EXPORT void revealSelection(SelectionRevealMode = SelectionRevealMode::Reveal, const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, RevealExtentOption = DoNotRevealExtent); + WEBCORE_EXPORT void setSelectionFromNone(); bool shouldShowBlockCursor() const { return m_shouldShowBlockCursor; } void setShouldShowBlockCursor(bool); @@ -297,7 +275,12 @@ public: private: enum EPositionType { START, END, BASE, EXTENT }; - void respondToNodeModification(Node*, bool baseRemoved, bool extentRemoved, bool startRemoved, bool endRemoved); + void updateAndRevealSelection(const AXTextStateChangeIntent&); + void updateDataDetectorsForSelection(); + + bool setSelectionWithoutUpdatingAppearance(const VisibleSelection&, SetSelectionOptions, CursorAlignOnScroll, TextGranularity); + + void respondToNodeModification(Node&, bool baseRemoved, bool extentRemoved, bool startRemoved, bool endRemoved); TextDirection directionOfEnclosingBlock(); TextDirection directionOfSelection(); @@ -308,25 +291,37 @@ private: VisiblePosition modifyExtendingRight(TextGranularity); VisiblePosition modifyExtendingForward(TextGranularity); - VisiblePosition modifyMovingRight(TextGranularity); - VisiblePosition modifyMovingForward(TextGranularity); + VisiblePosition modifyMovingRight(TextGranularity, bool* reachedBoundary = nullptr); + VisiblePosition modifyMovingForward(TextGranularity, bool* reachedBoundary = nullptr); VisiblePosition modifyExtendingLeft(TextGranularity); VisiblePosition modifyExtendingBackward(TextGranularity); - VisiblePosition modifyMovingLeft(TextGranularity); - VisiblePosition modifyMovingBackward(TextGranularity); + VisiblePosition modifyMovingLeft(TextGranularity, bool* reachedBoundary = nullptr); + VisiblePosition modifyMovingBackward(TextGranularity, bool* reachedBoundary = nullptr); LayoutUnit lineDirectionPointForBlockDirectionNavigation(EPositionType); + AXTextStateChangeIntent textSelectionIntent(EAlteration, SelectionDirection, TextGranularity); #if HAVE(ACCESSIBILITY) - void notifyAccessibilityForSelectionChange(); + void notifyAccessibilityForSelectionChange(const AXTextStateChangeIntent&); +#else + void notifyAccessibilityForSelectionChange(const AXTextStateChangeIntent&) { } #endif + void updateSelectionCachesIfSelectionIsInsideTextFormControl(EUserTriggered); + + void selectFrameElementInParentIfFullySelected(); + void setFocusedElementIfNeeded(); void focusedOrActiveStateChanged(); - void caretBlinkTimerFired(Timer<FrameSelection>&); + void caretBlinkTimerFired(); + + void updateAppearanceAfterLayoutOrStyleChange(); + void appearanceUpdateTimerFired(); void setCaretVisibility(CaretVisibility); + bool recomputeCaretRect(); + void invalidateCaretRect(); bool dispatchSelectStart(); @@ -342,21 +337,25 @@ private: RefPtr<EditingStyle> m_typingStyle; - Timer<FrameSelection> m_caretBlinkTimer; + Timer m_caretBlinkTimer; + Timer m_appearanceUpdateTimer; // The painted bounds of the caret in absolute coordinates IntRect m_absCaretBounds; + bool m_caretInsidePositionFixed : 1; bool m_absCaretBoundsDirty : 1; bool m_caretPaint : 1; bool m_isCaretBlinkingSuspended : 1; bool m_focused : 1; bool m_shouldShowBlockCursor : 1; + bool m_pendingSelectionUpdate : 1; + bool m_shouldRevealSelection : 1; + bool m_alwaysAlignCursorOnScrollWhenRevealingSelection : 1; #if PLATFORM(IOS) bool m_updateAppearanceEnabled : 1; bool m_caretBlinks : 1; Color m_caretColor; - int m_closeTypingSuppressions; - int m_scrollingSuppressCount; + int m_scrollingSuppressCount { 0 }; #endif }; @@ -367,7 +366,7 @@ inline EditingStyle* FrameSelection::typingStyle() const inline void FrameSelection::clearTypingStyle() { - m_typingStyle.clear(); + m_typingStyle = nullptr; } inline void FrameSelection::setTypingStyle(PassRefPtr<EditingStyle> style) @@ -375,9 +374,9 @@ inline void FrameSelection::setTypingStyle(PassRefPtr<EditingStyle> style) m_typingStyle = style; } -#if !(PLATFORM(MAC) || PLATFORM(GTK) || PLATFORM(EFL)) +#if !(PLATFORM(COCOA) || PLATFORM(GTK)) #if HAVE(ACCESSIBILITY) -inline void FrameSelection::notifyAccessibilityForSelectionChange() +inline void FrameSelection::notifyAccessibilityForSelectionChange(const AXTextStateChangeIntent&) { } #endif @@ -385,10 +384,8 @@ inline void FrameSelection::notifyAccessibilityForSelectionChange() } // namespace WebCore -#ifndef NDEBUG -// Outside the WebCore namespace for ease of invocation from gdb. +#if ENABLE(TREE_DEBUGGING) +// Outside the WebCore namespace for ease of invocation from the debugger. void showTree(const WebCore::FrameSelection&); void showTree(const WebCore::FrameSelection*); #endif - -#endif // FrameSelection_h diff --git a/Source/WebCore/editing/HTMLInterchange.cpp b/Source/WebCore/editing/HTMLInterchange.cpp index 2f7295118..03f94abff 100644 --- a/Source/WebCore/editing/HTMLInterchange.cpp +++ b/Source/WebCore/editing/HTMLInterchange.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -30,6 +30,7 @@ #include "RenderText.h" #include "Text.h" #include "TextIterator.h" +#include "htmlediting.h" #include <wtf/text/StringBuilder.h> #include <wtf/unicode/CharacterNames.h> @@ -50,10 +51,10 @@ String convertHTMLTextToInterchangeFormat(const String& in, const Text* node) unsigned consumed = 0; while (i < in.length()) { consumed = 1; - if (isCollapsibleWhitespace(in[i])) { + if (deprecatedIsCollapsibleWhitespace(in[i])) { // count number of adjoining spaces unsigned j = i + 1; - while (j < in.length() && isCollapsibleWhitespace(in[j])) + while (j < in.length() && deprecatedIsCollapsibleWhitespace(in[j])) j++; unsigned count = j - i; consumed = count; diff --git a/Source/WebCore/editing/HTMLInterchange.h b/Source/WebCore/editing/HTMLInterchange.h index 4029ea27f..657b1bf5b 100644 --- a/Source/WebCore/editing/HTMLInterchange.h +++ b/Source/WebCore/editing/HTMLInterchange.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef HTMLInterchange_h -#define HTMLInterchange_h +#pragma once #include <wtf/Forward.h> @@ -42,6 +41,4 @@ enum EAnnotateForInterchange { DoNotAnnotateForInterchange, AnnotateForInterchan String convertHTMLTextToInterchangeFormat(const String&, const Text*); -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/editing/IndentOutdentCommand.cpp b/Source/WebCore/editing/IndentOutdentCommand.cpp index 63958bb4d..0729fc774 100644 --- a/Source/WebCore/editing/IndentOutdentCommand.cpp +++ b/Source/WebCore/editing/IndentOutdentCommand.cpp @@ -10,12 +10,12 @@ * 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 + * 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 COMPUTER, INC. OR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (IndentOutdentCommandINCLUDING, BUT NOT LIMITED TO, + * 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 @@ -28,14 +28,15 @@ #include "Document.h" #include "ElementTraversal.h" -#include "HTMLElement.h" +#include "HTMLBRElement.h" #include "HTMLNames.h" +#include "HTMLOListElement.h" +#include "HTMLUListElement.h" #include "InsertLineBreakCommand.h" #include "InsertListCommand.h" -#include "RenderObject.h" +#include "RenderElement.h" #include "SplitElementCommand.h" #include "Text.h" -#include "TextIterator.h" #include "VisibleUnits.h" #include "htmlediting.h" @@ -48,10 +49,9 @@ static bool isListOrIndentBlockquote(const Node* node) return node && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(blockquoteTag)); } -IndentOutdentCommand::IndentOutdentCommand(Document& document, EIndentType typeOfAction, int marginInPixels) +IndentOutdentCommand::IndentOutdentCommand(Document& document, EIndentType typeOfAction) : ApplyBlockElementCommand(document, blockquoteTag, "margin: 0 0 0 40px; border: none; padding: 0px;") , m_typeOfAction(typeOfAction) - , m_marginInPixels(marginInPixels) { } @@ -66,15 +66,18 @@ bool IndentOutdentCommand::tryIndentingAsListItem(const Position& start, const P // Find the block that we want to indent. If it's not a list item (e.g., a div inside a list item), we bail out. RefPtr<Element> selectedListItem = enclosingBlock(lastNodeInSelectedParagraph); - // FIXME: we need to deal with the case where there is no li (malformed HTML) - if (!selectedListItem->hasTagName(liTag)) + if (!selectedListItem || !selectedListItem->hasTagName(liTag)) return false; // FIXME: previousElementSibling does not ignore non-rendered content like <span></span>. Should we? - RefPtr<Element> previousList = ElementTraversal::previousSibling(selectedListItem.get()); - RefPtr<Element> nextList = ElementTraversal::nextSibling(selectedListItem.get()); + RefPtr<Element> previousList = ElementTraversal::previousSibling(*selectedListItem); + RefPtr<Element> nextList = ElementTraversal::nextSibling(*selectedListItem); - RefPtr<Element> newList = document().createElement(listNode->tagQName(), false); + RefPtr<Element> newList; + if (is<HTMLUListElement>(*listNode)) + newList = HTMLUListElement::create(document()); + else + newList = HTMLOListElement::create(document()); insertNodeBefore(newList, selectedListItem); moveParagraphWithClones(start, end, newList.get(), selectedListItem.get()); @@ -109,7 +112,7 @@ void IndentOutdentCommand::indentIntoBlockquote(const Position& start, const Pos // Create a new blockquote and insert it as a child of the root editable element. We accomplish // this by splitting all parents of the current paragraph up to that point. targetBlockquote = createBlockElement(); - if (outerBlock == start.containerNode()) + if (outerBlock == nodeToSplitTo) insertNodeAt(targetBlockquote, start); else insertNodeBefore(targetBlockquote, outerBlock); @@ -124,7 +127,7 @@ void IndentOutdentCommand::outdentParagraph() VisiblePosition visibleStartOfParagraph = startOfParagraph(endingSelection().visibleStart()); VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph); - Node* enclosingNode = enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote); + auto* enclosingNode = downcast<HTMLElement>(enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote)); if (!enclosingNode || !enclosingNode->parentNode()->hasEditableStyle()) // We can't outdent if there is no place to go! return; @@ -157,7 +160,7 @@ void IndentOutdentCommand::outdentParagraph() if (splitPointParent->hasTagName(blockquoteTag) && !splitPoint->hasTagName(blockquoteTag) && splitPointParent->parentNode()->hasEditableStyle()) // We can't outdent if there is no place to go! - splitElement(toElement(splitPointParent), splitPoint); + splitElement(downcast<Element>(splitPointParent), splitPoint); } } @@ -165,24 +168,26 @@ void IndentOutdentCommand::outdentParagraph() visibleStartOfParagraph = VisiblePosition(visibleStartOfParagraph.deepEquivalent()); visibleEndOfParagraph = VisiblePosition(visibleEndOfParagraph.deepEquivalent()); if (visibleStartOfParagraph.isNotNull() && !isStartOfParagraph(visibleStartOfParagraph)) - insertNodeAt(createBreakElement(document()), visibleStartOfParagraph.deepEquivalent()); + insertNodeAt(HTMLBRElement::create(document()), visibleStartOfParagraph.deepEquivalent()); if (visibleEndOfParagraph.isNotNull() && !isEndOfParagraph(visibleEndOfParagraph)) - insertNodeAt(createBreakElement(document()), visibleEndOfParagraph.deepEquivalent()); + insertNodeAt(HTMLBRElement::create(document()), visibleEndOfParagraph.deepEquivalent()); return; } - Node* enclosingBlockFlow = enclosingBlock(visibleStartOfParagraph.deepEquivalent().deprecatedNode()); + + auto* startOfParagraphNode = visibleStartOfParagraph.deepEquivalent().deprecatedNode(); + auto* enclosingBlockFlow = enclosingBlock(startOfParagraphNode); RefPtr<Node> splitBlockquoteNode = enclosingNode; if (enclosingBlockFlow != enclosingNode) - splitBlockquoteNode = splitTreeToNode(enclosingBlockFlow, enclosingNode, true); + splitBlockquoteNode = splitTreeToNode(startOfParagraphNode, enclosingNode, true); else { // We split the blockquote at where we start outdenting. - Node* highestInlineNode = highestEnclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), isInline, CannotCrossEditingBoundary, enclosingBlockFlow); - splitElement(toElement(enclosingNode), (highestInlineNode) ? highestInlineNode : visibleStartOfParagraph.deepEquivalent().deprecatedNode()); + auto* highestInlineNode = highestEnclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), isInline, CannotCrossEditingBoundary, enclosingBlockFlow); + splitElement(enclosingNode, highestInlineNode ? highestInlineNode : visibleStartOfParagraph.deepEquivalent().deprecatedNode()); } - RefPtr<Node> placeholder = createBreakElement(document()); - insertNodeBefore(placeholder, splitBlockquoteNode); - moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), positionBeforeNode(placeholder.get()), true); + auto placeholder = HTMLBRElement::create(document()); + insertNodeBefore(placeholder.copyRef(), splitBlockquoteNode); + moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), positionBeforeNode(placeholder.ptr()), true); } // FIXME: We should merge this function with ApplyBlockElementCommand::formatSelection @@ -194,7 +199,7 @@ void IndentOutdentCommand::outdentRegion(const VisiblePosition& startOfSelection outdentParagraph(); return; } - + Position originalSelectionEnd = endingSelection().end(); VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next()); @@ -205,16 +210,16 @@ void IndentOutdentCommand::outdentRegion(const VisiblePosition& startOfSelection setEndingSelection(VisibleSelection(originalSelectionEnd, DOWNSTREAM)); else setEndingSelection(endOfCurrentParagraph); - + outdentParagraph(); - + // outdentParagraph could move more than one paragraph if the paragraph // is in a list item. As a result, endAfterSelection and endOfNextParagraph // could refer to positions no longer in the document. - if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().anchorNode()->inDocument()) + if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().anchorNode()->isConnected()) break; - - if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().anchorNode()->inDocument()) { + + if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().anchorNode()->isConnected()) { endOfCurrentParagraph = endingSelection().end(); endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); } @@ -233,7 +238,7 @@ void IndentOutdentCommand::formatSelection(const VisiblePosition& startOfSelecti void IndentOutdentCommand::formatRange(const Position& start, const Position& end, const Position&, RefPtr<Element>& blockquoteForNextIndent) { if (tryIndentingAsListItem(start, end)) - blockquoteForNextIndent = 0; + blockquoteForNextIndent = nullptr; else indentIntoBlockquote(start, end, blockquoteForNextIndent); } diff --git a/Source/WebCore/editing/IndentOutdentCommand.h b/Source/WebCore/editing/IndentOutdentCommand.h index 4ebafe96b..c30f74396 100644 --- a/Source/WebCore/editing/IndentOutdentCommand.h +++ b/Source/WebCore/editing/IndentOutdentCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef IndentOutdentCommand_h -#define IndentOutdentCommand_h +#pragma once #include "ApplyBlockElementCommand.h" #include "EditAction.h" @@ -34,17 +33,17 @@ namespace WebCore { class IndentOutdentCommand : public ApplyBlockElementCommand { public: enum EIndentType { Indent, Outdent }; - static PassRefPtr<IndentOutdentCommand> create(Document& document, EIndentType type, int marginInPixels = 0) + static Ref<IndentOutdentCommand> create(Document& document, EIndentType type) { - return adoptRef(new IndentOutdentCommand(document, type, marginInPixels)); + return adoptRef(*new IndentOutdentCommand(document, type)); } - virtual bool preservesTypingStyle() const { return true; } + bool preservesTypingStyle() const override { return true; } private: - IndentOutdentCommand(Document&, EIndentType, int marginInPixels); + IndentOutdentCommand(Document&, EIndentType); - virtual EditAction editingAction() const { return m_typeOfAction == Indent ? EditActionIndent : EditActionOutdent; } + EditAction editingAction() const override { return m_typeOfAction == Indent ? EditActionIndent : EditActionOutdent; } void indentRegion(const VisiblePosition&, const VisiblePosition&); void outdentRegion(const VisiblePosition&, const VisiblePosition&); @@ -52,13 +51,10 @@ private: bool tryIndentingAsListItem(const Position&, const Position&); void indentIntoBlockquote(const Position&, const Position&, RefPtr<Element>&); - void formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection); - void formatRange(const Position& start, const Position& end, const Position& endOfSelection, RefPtr<Element>& blockquoteForNextIndent); + void formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection) override; + void formatRange(const Position& start, const Position& end, const Position& endOfSelection, RefPtr<Element>& blockquoteForNextIndent) override; EIndentType m_typeOfAction; - int m_marginInPixels; }; } // namespace WebCore - -#endif // IndentOutdentCommand_h diff --git a/Source/WebCore/editing/InsertIntoTextNodeCommand.cpp b/Source/WebCore/editing/InsertIntoTextNodeCommand.cpp index 370d4bf4e..9d2894e3e 100644 --- a/Source/WebCore/editing/InsertIntoTextNodeCommand.cpp +++ b/Source/WebCore/editing/InsertIntoTextNodeCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -26,21 +26,21 @@ #include "config.h" #include "InsertIntoTextNodeCommand.h" -#include "AXObjectCache.h" #include "Document.h" -#include "ExceptionCodePlaceholder.h" +#include "Frame.h" #include "RenderText.h" #include "Settings.h" #include "Text.h" + #if PLATFORM(IOS) #include "RenderText.h" #endif namespace WebCore { -InsertIntoTextNodeCommand::InsertIntoTextNodeCommand(PassRefPtr<Text> node, unsigned offset, const String& text) - : SimpleEditCommand(node->document()) - , m_node(node) +InsertIntoTextNodeCommand::InsertIntoTextNodeCommand(RefPtr<Text>&& node, unsigned offset, const String& text, EditAction editingAction) + : SimpleEditCommand(node->document(), editingAction) + , m_node(WTFMove(node)) , m_offset(offset) , m_text(text) { @@ -51,8 +51,7 @@ InsertIntoTextNodeCommand::InsertIntoTextNodeCommand(PassRefPtr<Text> node, unsi void InsertIntoTextNodeCommand::doApply() { - // FIXME: EditCommand should always have a Frame, so going through Document for Settings shouldn't be necessary. - bool passwordEchoEnabled = document().settings() && document().settings()->passwordEchoEnabled(); + bool passwordEchoEnabled = frame().settings().passwordEchoEnabled(); if (passwordEchoEnabled) document().updateLayoutIgnorePendingStylesheets(); @@ -60,42 +59,40 @@ void InsertIntoTextNodeCommand::doApply() return; if (passwordEchoEnabled) { - RenderText* renderText = m_node->renderer(); - if (renderText && renderText->isSecure()) - renderText->momentarilyRevealLastTypedCharacter(m_offset + m_text.length() - 1); + if (RenderText* renderText = m_node->renderer()) + renderText->momentarilyRevealLastTypedCharacter(m_offset + m_text.length()); } - m_node->insertData(m_offset, m_text, IGNORE_EXCEPTION); - - if (AXObjectCache* cache = document().existingAXObjectCache()) - cache->nodeTextChangeNotification(m_node.get(), AXObjectCache::AXTextInserted, m_offset, m_text); + m_node->insertData(m_offset, m_text); } #if PLATFORM(IOS) + +// FIXME: Why would reapply be iOS-specific? void InsertIntoTextNodeCommand::doReapply() { - ExceptionCode ec; - m_node->insertData(m_offset, m_text, ec); + // FIXME: Shouldn't this have a hasEditableStyle check? + + m_node->insertData(m_offset, m_text); } + #endif void InsertIntoTextNodeCommand::doUnapply() { if (!m_node->hasEditableStyle()) return; - - // Need to notify this before actually deleting the text - if (AXObjectCache* cache = document().existingAXObjectCache()) - cache->nodeTextChangeNotification(m_node.get(), AXObjectCache::AXTextDeleted, m_offset, m_text); - m_node->deleteData(m_offset, m_text.length(), IGNORE_EXCEPTION); + m_node->deleteData(m_offset, m_text.length()); } #ifndef NDEBUG + void InsertIntoTextNodeCommand::getNodesInCommand(HashSet<Node*>& nodes) { addNodeAndDescendants(m_node.get(), nodes); } + #endif } // namespace WebCore diff --git a/Source/WebCore/editing/InsertIntoTextNodeCommand.h b/Source/WebCore/editing/InsertIntoTextNodeCommand.h index f56133464..5b8822288 100644 --- a/Source/WebCore/editing/InsertIntoTextNodeCommand.h +++ b/Source/WebCore/editing/InsertIntoTextNodeCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef InsertIntoTextNodeCommand_h -#define InsertIntoTextNodeCommand_h +#pragma once #include "EditCommand.h" @@ -34,22 +33,25 @@ class Text; class InsertIntoTextNodeCommand : public SimpleEditCommand { public: - static PassRefPtr<InsertIntoTextNodeCommand> create(PassRefPtr<Text> node, unsigned offset, const String& text) + static Ref<InsertIntoTextNodeCommand> create(RefPtr<Text>&& node, unsigned offset, const String& text, EditAction editingAction = EditActionInsert) { - return adoptRef(new InsertIntoTextNodeCommand(node, offset, text)); + return adoptRef(*new InsertIntoTextNodeCommand(WTFMove(node), offset, text, editingAction)); } -private: - InsertIntoTextNodeCommand(PassRefPtr<Text> node, unsigned offset, const String& text); + const String& insertedText(); + +protected: + InsertIntoTextNodeCommand(RefPtr<Text>&& node, unsigned offset, const String& text, EditAction editingAction); - virtual void doApply() override; - virtual void doUnapply() override; +private: + void doApply() override; + void doUnapply() override; #if PLATFORM(IOS) - virtual void doReapply() override; + void doReapply() override; #endif #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override; + void getNodesInCommand(HashSet<Node*>&) override; #endif RefPtr<Text> m_node; @@ -57,6 +59,9 @@ private: String m_text; }; -} // namespace WebCore +inline const String& InsertIntoTextNodeCommand::insertedText() +{ + return m_text; +} -#endif // InsertIntoTextNodeCommand_h +} // namespace WebCore diff --git a/Source/WebCore/editing/InsertLineBreakCommand.cpp b/Source/WebCore/editing/InsertLineBreakCommand.cpp index 3cc8996d0..812c0b46f 100644 --- a/Source/WebCore/editing/InsertLineBreakCommand.cpp +++ b/Source/WebCore/editing/InsertLineBreakCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2005, 2006 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -29,7 +29,8 @@ #include "Document.h" #include "Frame.h" #include "FrameSelection.h" -#include "HTMLElement.h" +#include "HTMLBRElement.h" +#include "HTMLHRElement.h" #include "HTMLNames.h" #include "HTMLTableElement.h" #include "RenderElement.h" @@ -52,45 +53,45 @@ bool InsertLineBreakCommand::preservesTypingStyle() const return true; } -void InsertLineBreakCommand::insertNodeAfterPosition(Node* node, const Position& pos) +void InsertLineBreakCommand::insertNodeAfterPosition(Node* node, const Position& position) { // Insert the BR after the caret position. In the case the // position is a block, do an append. We don't want to insert // the BR *after* the block. - Element* cb = deprecatedEnclosingBlockFlowElement(pos.deprecatedNode()); - if (cb == pos.deprecatedNode()) - appendNode(node, cb); + auto* element = deprecatedEnclosingBlockFlowElement(position.deprecatedNode()); + if (element == position.deprecatedNode()) + appendNode(node, element); else - insertNodeAfter(node, pos.deprecatedNode()); + insertNodeAfter(node, position.deprecatedNode()); } -void InsertLineBreakCommand::insertNodeBeforePosition(Node* node, const Position& pos) +void InsertLineBreakCommand::insertNodeBeforePosition(Node* node, const Position& position) { // Insert the BR after the caret position. In the case the // position is a block, do an append. We don't want to insert // the BR *before* the block. - Element* cb = deprecatedEnclosingBlockFlowElement(pos.deprecatedNode()); - if (cb == pos.deprecatedNode()) - appendNode(node, cb); + auto* element = deprecatedEnclosingBlockFlowElement(position.deprecatedNode()); + if (element == position.deprecatedNode()) + appendNode(node, element); else - insertNodeBefore(node, pos.deprecatedNode()); + insertNodeBefore(node, position.deprecatedNode()); } // Whether we should insert a break element or a '\n'. -bool InsertLineBreakCommand::shouldUseBreakElement(const Position& insertionPos) +bool InsertLineBreakCommand::shouldUseBreakElement(const Position& position) { // An editing position like [input, 0] actually refers to the position before // the input element, and in that case we need to check the input element's // parent's renderer. - Position p(insertionPos.parentAnchoredEquivalent()); - return p.deprecatedNode()->renderer() && !p.deprecatedNode()->renderer()->style().preserveNewline(); + auto* node = position.parentAnchoredEquivalent().deprecatedNode(); + return node->renderer() && !node->renderer()->style().preserveNewline(); } void InsertLineBreakCommand::doApply() { deleteSelection(); VisibleSelection selection = endingSelection(); - if (!selection.isNonOrphanedCaretOrRange()) + if (selection.isNoneOrOrphaned()) return; VisiblePosition caret(selection.visibleStart()); @@ -99,60 +100,59 @@ void InsertLineBreakCommand::doApply() if (caret.isNull()) return; - Position pos(caret.deepEquivalent()); + Position position(caret.deepEquivalent()); - pos = positionAvoidingSpecialElementBoundary(pos); - - pos = positionOutsideTabSpan(pos); + position = positionAvoidingSpecialElementBoundary(position); + position = positionOutsideTabSpan(position); RefPtr<Node> nodeToInsert; - if (shouldUseBreakElement(pos)) - nodeToInsert = createBreakElement(document()); + if (shouldUseBreakElement(position)) + nodeToInsert = HTMLBRElement::create(document()); else nodeToInsert = document().createTextNode("\n"); // FIXME: Need to merge text nodes when inserting just after or before text. if (isEndOfParagraph(caret) && !lineBreakExistsAtVisiblePosition(caret)) { - bool needExtraLineBreak = !pos.deprecatedNode()->hasTagName(hrTag) && !isHTMLTableElement(pos.deprecatedNode()); + bool needExtraLineBreak = !is<HTMLHRElement>(*position.deprecatedNode()) && !is<HTMLTableElement>(*position.deprecatedNode()); - insertNodeAt(nodeToInsert.get(), pos); + insertNodeAt(nodeToInsert.get(), position); if (needExtraLineBreak) insertNodeBefore(nodeToInsert->cloneNode(false), nodeToInsert); VisiblePosition endingPosition(positionBeforeNode(nodeToInsert.get())); setEndingSelection(VisibleSelection(endingPosition, endingSelection().isDirectional())); - } else if (pos.deprecatedEditingOffset() <= caretMinOffset(pos.deprecatedNode())) { - insertNodeAt(nodeToInsert.get(), pos); + } else if (position.deprecatedEditingOffset() <= caretMinOffset(*position.deprecatedNode())) { + insertNodeAt(nodeToInsert.get(), position); // Insert an extra br or '\n' if the just inserted one collapsed. if (!isStartOfParagraph(positionBeforeNode(nodeToInsert.get()))) - insertNodeBefore(nodeToInsert->cloneNode(false).get(), nodeToInsert.get()); + insertNodeBefore(nodeToInsert->cloneNode(false), nodeToInsert.get()); setEndingSelection(VisibleSelection(positionInParentAfterNode(nodeToInsert.get()), DOWNSTREAM, endingSelection().isDirectional())); // If we're inserting after all of the rendered text in a text node, or into a non-text node, // a simple insertion is sufficient. - } else if (pos.deprecatedEditingOffset() >= caretMaxOffset(pos.deprecatedNode()) || !pos.deprecatedNode()->isTextNode()) { - insertNodeAt(nodeToInsert.get(), pos); + } else if (position.deprecatedEditingOffset() >= caretMaxOffset(*position.deprecatedNode()) || !is<Text>(*position.deprecatedNode())) { + insertNodeAt(nodeToInsert.get(), position); setEndingSelection(VisibleSelection(positionInParentAfterNode(nodeToInsert.get()), DOWNSTREAM, endingSelection().isDirectional())); - } else if (pos.deprecatedNode()->isTextNode()) { + } else if (is<Text>(*position.deprecatedNode())) { // Split a text node - Text* textNode = toText(pos.deprecatedNode()); - splitTextNode(textNode, pos.deprecatedEditingOffset()); - insertNodeBefore(nodeToInsert, textNode); - Position endingPosition = firstPositionInNode(textNode); + Text& textNode = downcast<Text>(*position.deprecatedNode()); + splitTextNode(&textNode, position.deprecatedEditingOffset()); + insertNodeBefore(nodeToInsert, &textNode); + Position endingPosition = firstPositionInNode(&textNode); // Handle whitespace that occurs after the split document().updateLayoutIgnorePendingStylesheets(); if (!endingPosition.isRenderedCharacter()) { - Position positionBeforeTextNode(positionInParentBeforeNode(textNode)); + Position positionBeforeTextNode(positionInParentBeforeNode(&textNode)); // Clear out all whitespace and insert one non-breaking space deleteInsignificantTextDownstream(endingPosition); - ASSERT(!textNode->renderer() || textNode->renderer()->style().collapseWhiteSpace()); + ASSERT(!textNode.renderer() || textNode.renderer()->style().collapseWhiteSpace()); // Deleting insignificant whitespace will remove textNode if it contains nothing but insignificant whitespace. - if (textNode->inDocument()) - insertTextIntoNode(textNode, 0, nonBreakingSpaceString()); + if (textNode.isConnected()) + insertTextIntoNode(&textNode, 0, nonBreakingSpaceString()); else { RefPtr<Text> nbspNode = document().createTextNode(nonBreakingSpaceString()); insertNodeAt(nbspNode.get(), positionBeforeTextNode); diff --git a/Source/WebCore/editing/InsertLineBreakCommand.h b/Source/WebCore/editing/InsertLineBreakCommand.h index d48847a93..2b3d1bfa7 100644 --- a/Source/WebCore/editing/InsertLineBreakCommand.h +++ b/Source/WebCore/editing/InsertLineBreakCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef InsertLineBreakCommand_h -#define InsertLineBreakCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -32,17 +31,17 @@ namespace WebCore { class InsertLineBreakCommand : public CompositeEditCommand { public: - static PassRefPtr<InsertLineBreakCommand> create(Document& document) + static Ref<InsertLineBreakCommand> create(Document& document) { - return adoptRef(new InsertLineBreakCommand(document)); + return adoptRef(*new InsertLineBreakCommand(document)); } private: explicit InsertLineBreakCommand(Document&); - virtual void doApply(); + void doApply() override; - virtual bool preservesTypingStyle() const; + bool preservesTypingStyle() const override; void insertNodeAfterPosition(Node*, const Position&); void insertNodeBeforePosition(Node*, const Position&); @@ -50,5 +49,3 @@ private: }; } // namespace WebCore - -#endif // InsertLineBreakCommand_h diff --git a/Source/WebCore/editing/InsertListCommand.cpp b/Source/WebCore/editing/InsertListCommand.cpp index d3ead9780..894a7cffc 100644 --- a/Source/WebCore/editing/InsertListCommand.cpp +++ b/Source/WebCore/editing/InsertListCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -24,15 +24,16 @@ */ #include "config.h" -#include "Element.h" -#include "ElementTraversal.h" #include "InsertListCommand.h" -#include "ExceptionCodePlaceholder.h" -#include "htmlediting.h" -#include "HTMLElement.h" + +#include "ElementTraversal.h" +#include "HTMLBRElement.h" +#include "HTMLLIElement.h" #include "HTMLNames.h" -#include "TextIterator.h" +#include "HTMLUListElement.h" +#include "Range.h" #include "VisibleUnits.h" +#include "htmlediting.h" namespace WebCore { @@ -46,7 +47,7 @@ static Node* enclosingListChild(Node* node, Node* listNode) return listChild; } -PassRefPtr<HTMLElement> InsertListCommand::insertList(Document& document, Type type) +RefPtr<HTMLElement> InsertListCommand::insertList(Document& document, Type type) { RefPtr<InsertListCommand> insertCommand = create(document, type); insertCommand->apply(); @@ -55,15 +56,15 @@ PassRefPtr<HTMLElement> InsertListCommand::insertList(Document& document, Type t HTMLElement* InsertListCommand::fixOrphanedListChild(Node* node) { - RefPtr<HTMLElement> listElement = createUnorderedListElement(document()); - insertNodeBefore(listElement, node); + auto listElement = HTMLUListElement::create(document()); + insertNodeBefore(listElement.copyRef(), node); removeNode(node); - appendNode(node, listElement); - m_listElement = listElement; - return listElement.get(); + appendNode(node, listElement.copyRef()); + m_listElement = listElement.copyRef(); + return listElement.ptr(); } -PassRefPtr<HTMLElement> InsertListCommand::mergeWithNeighboringLists(PassRefPtr<HTMLElement> passedList) +RefPtr<HTMLElement> InsertListCommand::mergeWithNeighboringLists(PassRefPtr<HTMLElement> passedList) { RefPtr<HTMLElement> list = passedList; Element* previousList = list->previousElementSibling(); @@ -71,17 +72,17 @@ PassRefPtr<HTMLElement> InsertListCommand::mergeWithNeighboringLists(PassRefPtr< mergeIdenticalElements(previousList, list); if (!list) - return 0; - Element* sibling = ElementTraversal::nextSibling(list.get()); - if (!sibling || !sibling->isHTMLElement()) - return list.release(); + return nullptr; + Element* sibling = ElementTraversal::nextSibling(*list); + if (!is<HTMLElement>(sibling)) + return list; - RefPtr<HTMLElement> nextList = toHTMLElement(sibling); + RefPtr<HTMLElement> nextList = downcast<HTMLElement>(sibling); if (canMergeLists(list.get(), nextList.get())) { mergeIdenticalElements(list, nextList); - return nextList.release(); + return nextList; } - return list.release(); + return list; } bool InsertListCommand::selectionHasListOfType(const VisibleSelection& selection, const QualifiedName& listTag) @@ -110,12 +111,9 @@ InsertListCommand::InsertListCommand(Document& document, Type type) void InsertListCommand::doApply() { - if (!endingSelection().isNonOrphanedCaretOrRange()) + if (endingSelection().isNoneOrOrphaned() || !endingSelection().isContentRichlyEditable()) return; - if (!endingSelection().rootEditableElement()) - return; - VisiblePosition visibleEnd = endingSelection().visibleEnd(); VisiblePosition visibleStart = endingSelection().visibleStart(); // When a selection ends at the start of a paragraph, we rarely paint @@ -126,10 +124,13 @@ void InsertListCommand::doApply() // FIXME: We paint the gap before some paragraphs that are indented with left // margin/padding, but not others. We should make the gap painting more consistent and // then use a left margin/padding rule here. - if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd, CanSkipOverEditingBoundary)) + if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd, CanSkipOverEditingBoundary)) { setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(CannotCrossEditingBoundary), endingSelection().isDirectional())); + if (!endingSelection().rootEditableElement()) + return; + } - const QualifiedName& listTag = (m_type == OrderedList) ? olTag : ulTag; + auto& listTag = (m_type == OrderedList) ? olTag : ulTag; if (endingSelection().isRange()) { VisibleSelection selection = selectionForParagraphIteration(endingSelection()); ASSERT(selection.isRange()); @@ -148,7 +149,7 @@ void InsertListCommand::doApply() // infinite loop and because there is no more work to be done. // FIXME(<rdar://problem/5983974>): The endingSelection() may be incorrect here. Compute // the new location of endOfSelection and use it as the end of the new selection. - if (!startOfLastParagraph.deepEquivalent().anchorNode()->inDocument()) + if (!startOfLastParagraph.deepEquivalent().anchorNode()->isConnected()) return; setEndingSelection(startOfCurrentParagraph); @@ -191,7 +192,12 @@ void InsertListCommand::doApply() doApplyForSingleParagraph(false, listTag, endingSelection().firstRange().get()); } -void InsertListCommand::doApplyForSingleParagraph(bool forceCreateList, const QualifiedName& listTag, Range* currentSelection) +EditAction InsertListCommand::editingAction() const +{ + return m_type == OrderedList ? EditActionInsertOrderedList : EditActionInsertUnorderedList; +} + +void InsertListCommand::doApplyForSingleParagraph(bool forceCreateList, const HTMLQualifiedName& listTag, Range* currentSelection) { // FIXME: This will produce unexpected results for a selection that starts just before a // table and ends inside the first cell, selectionForParagraphIteration should probably @@ -206,31 +212,32 @@ void InsertListCommand::doApplyForSingleParagraph(bool forceCreateList, const Qu listNode = fixOrphanedListChild(listChildNode); listNode = mergeWithNeighboringLists(listNode); } - if (!listNode->hasTagName(listTag)) + if (!listNode->hasTagName(listTag)) { // listChildNode will be removed from the list and a list of type m_type will be created. switchListType = true; + } // If the list is of the desired type, and we are not removing the list, then exit early. if (!switchListType && forceCreateList) return; // If the entire list is selected, then convert the whole list. - if (switchListType && isNodeVisiblyContainedWithin(listNode.get(), currentSelection)) { - bool rangeStartIsInList = visiblePositionBeforeNode(listNode.get()) == currentSelection->startPosition(); - bool rangeEndIsInList = visiblePositionAfterNode(listNode.get()) == currentSelection->endPosition(); + if (switchListType && isNodeVisiblyContainedWithin(*listNode, *currentSelection)) { + bool rangeStartIsInList = visiblePositionBeforeNode(*listNode) == currentSelection->startPosition(); + bool rangeEndIsInList = visiblePositionAfterNode(*listNode) == currentSelection->endPosition(); RefPtr<HTMLElement> newList = createHTMLElement(document(), listTag); insertNodeBefore(newList, listNode); - Node* firstChildInList = enclosingListChild(VisiblePosition(firstPositionInNode(listNode.get())).deepEquivalent().deprecatedNode(), listNode.get()); - Node* outerBlock = isBlockFlowElement(firstChildInList) ? firstChildInList : listNode.get(); + auto* firstChildInList = enclosingListChild(VisiblePosition(firstPositionInNode(listNode.get())).deepEquivalent().deprecatedNode(), listNode.get()); + Node* outerBlock = firstChildInList && isBlockFlowElement(*firstChildInList) ? firstChildInList : listNode.get(); moveParagraphWithClones(firstPositionInNode(listNode.get()), lastPositionInNode(listNode.get()), newList.get(), outerBlock); // Manually remove listNode because moveParagraphWithClones sometimes leaves it behind in the document. // See the bug 33668 and editing/execCommand/insert-list-orphaned-item-with-nested-lists.html. // FIXME: This might be a bug in moveParagraphWithClones or deleteSelection. - if (listNode && listNode->inDocument()) + if (listNode && listNode->isConnected()) removeNode(listNode); newList = mergeWithNeighboringLists(newList); @@ -238,9 +245,9 @@ void InsertListCommand::doApplyForSingleParagraph(bool forceCreateList, const Qu // Restore the start and the end of current selection if they started inside listNode // because moveParagraphWithClones could have removed them. if (rangeStartIsInList && newList) - currentSelection->setStart(newList, 0, IGNORE_EXCEPTION); + currentSelection->setStart(*newList, 0); if (rangeEndIsInList && newList) - currentSelection->setEnd(newList, lastOffsetInNode(newList.get()), IGNORE_EXCEPTION); + currentSelection->setEnd(*newList, lastOffsetInNode(newList.get())); setEndingSelection(VisiblePosition(firstPositionInNode(newList.get()))); @@ -276,12 +283,12 @@ void InsertListCommand::unlistifyParagraph(const VisiblePosition& originalStart, } // When removing a list, we must always create a placeholder to act as a point of insertion // for the list content being removed. - RefPtr<Element> placeholder = createBreakElement(document()); + RefPtr<Element> placeholder = HTMLBRElement::create(document()); RefPtr<Element> nodeToInsert = placeholder; // If the content of the list item will be moved into another list, put it in a list item // so that we don't create an orphaned list child. if (enclosingList(listNode)) { - nodeToInsert = createListItemElement(document()); + nodeToInsert = HTMLLIElement::create(document()); appendNode(placeholder, nodeToInsert); } @@ -329,7 +336,7 @@ static Element* adjacentEnclosingList(const VisiblePosition& pos, const VisibleP return listNode; } -PassRefPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePosition& originalStart, const QualifiedName& listTag) +RefPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePosition& originalStart, const QualifiedName& listTag) { VisiblePosition start = startOfParagraph(originalStart, CanSkipOverEditingBoundary); VisiblePosition end = endOfParagraph(start, CanSkipOverEditingBoundary); @@ -338,29 +345,29 @@ PassRefPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePositio return 0; // Check for adjoining lists. - RefPtr<HTMLElement> listItemElement = createListItemElement(document()); - RefPtr<HTMLElement> placeholder = createBreakElement(document()); - appendNode(placeholder, listItemElement); + auto listItemElement = HTMLLIElement::create(document()); + auto placeholder = HTMLBRElement::create(document()); + appendNode(placeholder.copyRef(), listItemElement.copyRef()); // Place list item into adjoining lists. Element* previousList = adjacentEnclosingList(start.deepEquivalent(), start.previous(CannotCrossEditingBoundary), listTag); Element* nextList = adjacentEnclosingList(start.deepEquivalent(), end.next(CannotCrossEditingBoundary), listTag); RefPtr<HTMLElement> listElement; if (previousList) - appendNode(listItemElement, previousList); + appendNode(WTFMove(listItemElement), previousList); else if (nextList) - insertNodeAt(listItemElement, positionBeforeNode(nextList)); + insertNodeAt(WTFMove(listItemElement), positionBeforeNode(nextList)); else { // Create the list. listElement = createHTMLElement(document(), listTag); - appendNode(listItemElement, listElement); + appendNode(WTFMove(listItemElement), listElement); if (start == end && isBlock(start.deepEquivalent().deprecatedNode())) { // Inserting the list into an empty paragraph that isn't held open // by a br or a '\n', will invalidate start and end. Insert // a placeholder and then recompute start and end. - RefPtr<Node> placeholder = insertBlockPlaceholder(start.deepEquivalent()); - start = positionBeforeNode(placeholder.get()); + auto blockPlaceholder = insertBlockPlaceholder(start.deepEquivalent()); + start = positionBeforeNode(blockPlaceholder.get()); end = start; } @@ -388,7 +395,7 @@ PassRefPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePositio } } - moveParagraph(start, end, positionBeforeNode(placeholder.get()), true); + moveParagraph(start, end, positionBeforeNode(placeholder.ptr()), true); if (listElement) return mergeWithNeighboringLists(listElement); diff --git a/Source/WebCore/editing/InsertListCommand.h b/Source/WebCore/editing/InsertListCommand.h index a0d50d349..16c3370bf 100644 --- a/Source/WebCore/editing/InsertListCommand.h +++ b/Source/WebCore/editing/InsertListCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,44 +23,42 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef InsertListCommand_h -#define InsertListCommand_h +#pragma once #include "CompositeEditCommand.h" namespace WebCore { class HTMLElement; +class HTMLQualifiedName; -class InsertListCommand : public CompositeEditCommand { +class InsertListCommand final : public CompositeEditCommand { public: enum Type { OrderedList, UnorderedList }; - static PassRefPtr<InsertListCommand> create(Document& document, Type listType) + static Ref<InsertListCommand> create(Document& document, Type listType) { - return adoptRef(new InsertListCommand(document, listType)); + return adoptRef(*new InsertListCommand(document, listType)); } - static PassRefPtr<HTMLElement> insertList(Document&, Type); + static RefPtr<HTMLElement> insertList(Document&, Type); - virtual bool preservesTypingStyle() const { return true; } + bool preservesTypingStyle() const final { return true; } private: InsertListCommand(Document&, Type); - virtual void doApply(); - virtual EditAction editingAction() const { return EditActionInsertList; } + void doApply() final; + EditAction editingAction() const final; HTMLElement* fixOrphanedListChild(Node*); bool selectionHasListOfType(const VisibleSelection& selection, const QualifiedName&); - PassRefPtr<HTMLElement> mergeWithNeighboringLists(PassRefPtr<HTMLElement>); - void doApplyForSingleParagraph(bool forceCreateList, const QualifiedName&, Range* currentSelection); + RefPtr<HTMLElement> mergeWithNeighboringLists(PassRefPtr<HTMLElement>); + void doApplyForSingleParagraph(bool forceCreateList, const HTMLQualifiedName&, Range* currentSelection); void unlistifyParagraph(const VisiblePosition& originalStart, HTMLElement* listNode, Node* listChildNode); - PassRefPtr<HTMLElement> listifyParagraph(const VisiblePosition& originalStart, const QualifiedName& listTag); + RefPtr<HTMLElement> listifyParagraph(const VisiblePosition& originalStart, const QualifiedName& listTag); RefPtr<HTMLElement> m_listElement; Type m_type; }; } // namespace WebCore - -#endif // InsertListCommand_h diff --git a/Source/WebCore/editing/InsertNodeBeforeCommand.cpp b/Source/WebCore/editing/InsertNodeBeforeCommand.cpp index 7ac54e80a..dabf7c8a1 100644 --- a/Source/WebCore/editing/InsertNodeBeforeCommand.cpp +++ b/Source/WebCore/editing/InsertNodeBeforeCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -26,16 +26,15 @@ #include "config.h" #include "InsertNodeBeforeCommand.h" -#include "AXObjectCache.h" #include "Document.h" -#include "ExceptionCodePlaceholder.h" +#include "RenderElement.h" +#include "Text.h" #include "htmlediting.h" namespace WebCore { -InsertNodeBeforeCommand::InsertNodeBeforeCommand(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild, - ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) - : SimpleEditCommand(refChild->document()) +InsertNodeBeforeCommand::InsertNodeBeforeCommand(RefPtr<Node>&& insertChild, RefPtr<Node>&& refChild, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable, EditAction editingAction) + : SimpleEditCommand(refChild->document(), editingAction) , m_insertChild(insertChild) , m_refChild(refChild) , m_shouldAssumeContentIsAlwaysEditable(shouldAssumeContentIsAlwaysEditable) @@ -51,26 +50,19 @@ InsertNodeBeforeCommand::InsertNodeBeforeCommand(PassRefPtr<Node> insertChild, P void InsertNodeBeforeCommand::doApply() { ContainerNode* parent = m_refChild->parentNode(); - if (!parent || (m_shouldAssumeContentIsAlwaysEditable == DoNotAssumeContentIsAlwaysEditable && !parent->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))) + if (!parent || (m_shouldAssumeContentIsAlwaysEditable == DoNotAssumeContentIsAlwaysEditable && !isEditableNode(*parent))) return; - ASSERT(parent->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)); + ASSERT(isEditableNode(*parent)); - parent->insertBefore(m_insertChild.get(), m_refChild.get(), IGNORE_EXCEPTION); - - if (AXObjectCache* cache = document().existingAXObjectCache()) - cache->nodeTextChangeNotification(m_insertChild.get(), AXObjectCache::AXTextInserted, 0, m_insertChild->nodeValue()); + parent->insertBefore(*m_insertChild, m_refChild.get()); } void InsertNodeBeforeCommand::doUnapply() { - if (!m_insertChild->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) + if (!isEditableNode(*m_insertChild)) return; - // Need to notify this before actually deleting the text - if (AXObjectCache* cache = document().existingAXObjectCache()) - cache->nodeTextChangeNotification(m_insertChild.get(), AXObjectCache::AXTextDeleted, 0, m_insertChild->nodeValue()); - - m_insertChild->remove(IGNORE_EXCEPTION); + m_insertChild->remove(); } #ifndef NDEBUG diff --git a/Source/WebCore/editing/InsertNodeBeforeCommand.h b/Source/WebCore/editing/InsertNodeBeforeCommand.h index 1ad6f762b..48eab3d09 100644 --- a/Source/WebCore/editing/InsertNodeBeforeCommand.h +++ b/Source/WebCore/editing/InsertNodeBeforeCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef InsertNodeBeforeCommand_h -#define InsertNodeBeforeCommand_h +#pragma once #include "EditCommand.h" @@ -32,27 +31,26 @@ namespace WebCore { class InsertNodeBeforeCommand : public SimpleEditCommand { public: - static PassRefPtr<InsertNodeBeforeCommand> create(PassRefPtr<Node> childToInsert, PassRefPtr<Node> childToInsertBefore, - ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) + static Ref<InsertNodeBeforeCommand> create(RefPtr<Node>&& childToInsert, RefPtr<Node>&& childToInsertBefore, + ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable, EditAction editingAction = EditActionInsert) { - return adoptRef(new InsertNodeBeforeCommand(childToInsert, childToInsertBefore, shouldAssumeContentIsAlwaysEditable)); + return adoptRef(*new InsertNodeBeforeCommand(WTFMove(childToInsert), WTFMove(childToInsertBefore), shouldAssumeContentIsAlwaysEditable, editingAction)); } +protected: + InsertNodeBeforeCommand(RefPtr<Node>&& childToInsert, RefPtr<Node>&& childToInsertBefore, ShouldAssumeContentIsAlwaysEditable, EditAction); + private: - InsertNodeBeforeCommand(PassRefPtr<Node> childToInsert, PassRefPtr<Node> childToInsertBefore, ShouldAssumeContentIsAlwaysEditable); + void doApply() override; + void doUnapply() override; - virtual void doApply() override; - virtual void doUnapply() override; - #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override; + void getNodesInCommand(HashSet<Node*>&) override; #endif - + RefPtr<Node> m_insertChild; RefPtr<Node> m_refChild; ShouldAssumeContentIsAlwaysEditable m_shouldAssumeContentIsAlwaysEditable; }; } // namespace WebCore - -#endif // InsertNodeBeforeCommand_h diff --git a/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp b/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp index 2e32b69b4..4d1916009 100644 --- a/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp +++ b/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2005, 2006 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -28,7 +28,7 @@ #include "Document.h" #include "EditingStyle.h" -#include "HTMLElement.h" +#include "HTMLBRElement.h" #include "HTMLFormElement.h" #include "HTMLNames.h" #include "InsertLineBreakCommand.h" @@ -59,8 +59,8 @@ static Element* highestVisuallyEquivalentDivBelowRoot(Element* startBlock) return curBlock; } -InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(Document& document, bool mustUseDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea) - : CompositeEditCommand(document) +InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(Document& document, bool mustUseDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea, EditAction editingAction) + : CompositeEditCommand(document, editingAction) , m_mustUseDefaultParagraphElement(mustUseDefaultParagraphElement) , m_pasteBlockqutoeIntoUnquotedArea(pasteBlockqutoeIntoUnquotedArea) { @@ -131,24 +131,24 @@ void InsertParagraphSeparatorCommand::getAncestorsInsideBlock(const Node* insert } } -PassRefPtr<Element> InsertParagraphSeparatorCommand::cloneHierarchyUnderNewBlock(const Vector<RefPtr<Element>>& ancestors, PassRefPtr<Element> blockToInsert) +RefPtr<Element> InsertParagraphSeparatorCommand::cloneHierarchyUnderNewBlock(const Vector<RefPtr<Element>>& ancestors, PassRefPtr<Element> blockToInsert) { // Make clones of ancestors in between the start node and the start block. RefPtr<Element> parent = blockToInsert; for (size_t i = ancestors.size(); i != 0; --i) { - RefPtr<Element> child = ancestors[i - 1]->cloneElementWithoutChildren(); + auto child = ancestors[i - 1]->cloneElementWithoutChildren(document()); // It should always be okay to remove id from the cloned elements, since the originals are not deleted. child->removeAttribute(idAttr); - appendNode(child, parent); - parent = child.release(); + appendNode(child.ptr(), parent); + parent = WTFMove(child); } - return parent.release(); + return parent; } void InsertParagraphSeparatorCommand::doApply() { - if (!endingSelection().isNonOrphanedCaretOrRange()) + if (endingSelection().isNoneOrOrphaned()) return; Position insertionPosition = endingSelection().start(); @@ -169,7 +169,7 @@ void InsertParagraphSeparatorCommand::doApply() if (!startBlock || !startBlock->nonShadowBoundaryParentNode() || isTableCell(startBlock.get()) - || isHTMLFormElement(startBlock.get()) + || is<HTMLFormElement>(*startBlock) // FIXME: If the node is hidden, we don't have a canonical position so we will do the wrong thing for tables and <hr>. https://bugs.webkit.org/show_bug.cgi?id=40342 || (!canonicalPos.isNull() && canonicalPos.deprecatedNode()->renderer() && canonicalPos.deprecatedNode()->renderer()->isTable()) || (!canonicalPos.isNull() && canonicalPos.deprecatedNode()->hasTagName(hrTag))) { @@ -185,6 +185,9 @@ void InsertParagraphSeparatorCommand::doApply() // Adjust the insertion position after the delete insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition); VisiblePosition visiblePos(insertionPosition, affinity); + if (visiblePos.isNull()) + return; + calculateStyleBeforeInsertion(insertionPosition); //--------------------------------------------------------------------- @@ -207,7 +210,7 @@ void InsertParagraphSeparatorCommand::doApply() } else if (shouldUseDefaultParagraphElement(startBlock.get())) blockToInsert = createDefaultParagraphElement(document()); else - blockToInsert = startBlock->cloneElementWithoutChildren(); + blockToInsert = startBlock->cloneElementWithoutChildren(document()); //--------------------------------------------------------------------- // Handle case when position is in the last visible position in its block, @@ -227,7 +230,7 @@ void InsertParagraphSeparatorCommand::doApply() // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted. if (m_pasteBlockqutoeIntoUnquotedArea) { if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote)) - startBlock = toElement(highestBlockquote); + startBlock = downcast<Element>(highestBlockquote); } // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists). However, @@ -265,9 +268,8 @@ void InsertParagraphSeparatorCommand::doApply() // startBlock should always have children, otherwise isLastInBlock would be true and it's handled above. ASSERT(startBlock->firstChild()); refNode = startBlock->firstChild(); - } - else if (insertionPosition.deprecatedNode() == startBlock && nestNewBlock) { - refNode = startBlock->childNode(insertionPosition.deprecatedEditingOffset()); + } else if (insertionPosition.containerNode() == startBlock && nestNewBlock) { + refNode = startBlock->traverseToChildAt(insertionPosition.computeOffsetInContainerNode()); ASSERT(refNode); // must be true or we'd be in the end of block case } else refNode = insertionPosition.deprecatedNode(); @@ -297,7 +299,7 @@ void InsertParagraphSeparatorCommand::doApply() // it if visiblePos is at the start of a paragraph so that the // content will move down a line. if (isStartOfParagraph(visiblePos)) { - RefPtr<Element> br = createBreakElement(document()); + RefPtr<Element> br = HTMLBRElement::create(document()); insertNodeAt(br.get(), insertionPosition); insertionPosition = positionInParentAfterNode(br.get()); // If the insertion point is a break element, there is nothing else @@ -319,7 +321,7 @@ void InsertParagraphSeparatorCommand::doApply() // If the returned position lies either at the end or at the start of an element that is ignored by editing // we should move to its upstream or downstream position. - if (editingIgnoresContent(insertionPosition.deprecatedNode())) { + if (editingIgnoresContent(*insertionPosition.deprecatedNode())) { if (insertionPosition.atLastEditingPositionForNode()) insertionPosition = insertionPosition.downstream(); else if (insertionPosition.atFirstEditingPositionForNode()) @@ -331,16 +333,16 @@ void InsertParagraphSeparatorCommand::doApply() Position leadingWhitespace = insertionPosition.leadingWhitespacePosition(VP_DEFAULT_AFFINITY); // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions // after the preserved newline, causing the newline to be turned into a nbsp. - if (leadingWhitespace.isNotNull() && leadingWhitespace.deprecatedNode()->isTextNode()) { - Text* textNode = toText(leadingWhitespace.deprecatedNode()); - ASSERT(!textNode->renderer() || textNode->renderer()->style().collapseWhiteSpace()); - replaceTextInNodePreservingMarkers(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); + if (is<Text>(leadingWhitespace.deprecatedNode())) { + Text& textNode = downcast<Text>(*leadingWhitespace.deprecatedNode()); + ASSERT(!textNode.renderer() || textNode.renderer()->style().collapseWhiteSpace()); + replaceTextInNodePreservingMarkers(&textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); } // Split at pos if in the middle of a text node. Position positionAfterSplit; - if (insertionPosition.anchorType() == Position::PositionIsOffsetInAnchor && insertionPosition.containerNode()->isTextNode()) { - RefPtr<Text> textNode = toText(insertionPosition.containerNode()); + if (insertionPosition.anchorType() == Position::PositionIsOffsetInAnchor && is<Text>(*insertionPosition.containerNode())) { + RefPtr<Text> textNode = downcast<Text>(insertionPosition.containerNode()); bool atEnd = static_cast<unsigned>(insertionPosition.offsetInContainerNode()) >= textNode->length(); if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) { splitTextNode(textNode, insertionPosition.offsetInContainerNode()); @@ -368,7 +370,7 @@ void InsertParagraphSeparatorCommand::doApply() // created. All of the nodes, starting at visiblePos, are about to be added to the new paragraph // element. If the first node to be inserted won't be one that will hold an empty line open, add a br. if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos)) - appendNode(createBreakElement(document()).get(), blockToInsert.get()); + appendNode(HTMLBRElement::create(document()), blockToInsert.get()); // Move the start node and the siblings of the start node. if (VisiblePosition(insertionPosition) != VisiblePosition(positionBeforeNode(blockToInsert.get()))) { @@ -377,8 +379,8 @@ void InsertParagraphSeparatorCommand::doApply() n = insertionPosition.computeNodeAfterPosition(); else { Node* splitTo = insertionPosition.containerNode(); - if (splitTo->isTextNode() && insertionPosition.offsetInContainerNode() >= caretMaxOffset(splitTo)) - splitTo = NodeTraversal::next(splitTo, startBlock.get()); + if (is<Text>(*splitTo) && insertionPosition.offsetInContainerNode() >= caretMaxOffset(*splitTo)) + splitTo = NodeTraversal::next(*splitTo, startBlock.get()); ASSERT(splitTo); splitTreeToNode(splitTo, startBlock.get()); @@ -399,8 +401,8 @@ void InsertParagraphSeparatorCommand::doApply() // Clear out all whitespace and insert one non-breaking space ASSERT(!positionAfterSplit.containerNode()->renderer() || positionAfterSplit.containerNode()->renderer()->style().collapseWhiteSpace()); deleteInsignificantTextDownstream(positionAfterSplit); - if (positionAfterSplit.deprecatedNode()->isTextNode()) - insertTextIntoNode(toText(positionAfterSplit.containerNode()), 0, nonBreakingSpaceString()); + if (is<Text>(*positionAfterSplit.deprecatedNode())) + insertTextIntoNode(downcast<Text>(positionAfterSplit.containerNode()), 0, nonBreakingSpaceString()); } } diff --git a/Source/WebCore/editing/InsertParagraphSeparatorCommand.h b/Source/WebCore/editing/InsertParagraphSeparatorCommand.h index 815176d18..5efdd530d 100644 --- a/Source/WebCore/editing/InsertParagraphSeparatorCommand.h +++ b/Source/WebCore/editing/InsertParagraphSeparatorCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef InsertParagraphSeparatorCommand_h -#define InsertParagraphSeparatorCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -34,24 +33,24 @@ class EditingStyle; class InsertParagraphSeparatorCommand : public CompositeEditCommand { public: - static PassRefPtr<InsertParagraphSeparatorCommand> create(Document& document, bool useDefaultParagraphElement = false, bool pasteBlockqutoeIntoUnquotedArea = false) + static Ref<InsertParagraphSeparatorCommand> create(Document& document, bool useDefaultParagraphElement = false, bool pasteBlockqutoeIntoUnquotedArea = false, EditAction editingAction = EditActionInsert) { - return adoptRef(new InsertParagraphSeparatorCommand(document, useDefaultParagraphElement, pasteBlockqutoeIntoUnquotedArea)); + return adoptRef(*new InsertParagraphSeparatorCommand(document, useDefaultParagraphElement, pasteBlockqutoeIntoUnquotedArea, editingAction)); } private: - InsertParagraphSeparatorCommand(Document&, bool useDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea); + InsertParagraphSeparatorCommand(Document&, bool useDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea, EditAction); - virtual void doApply(); + void doApply() override; void calculateStyleBeforeInsertion(const Position&); void applyStyleAfterInsertion(Node* originalEnclosingBlock); void getAncestorsInsideBlock(const Node* insertionNode, Element* outerBlock, Vector<RefPtr<Element>>& ancestors); - PassRefPtr<Element> cloneHierarchyUnderNewBlock(const Vector<RefPtr<Element>>& ancestors, PassRefPtr<Element> blockToInsert); + RefPtr<Element> cloneHierarchyUnderNewBlock(const Vector<RefPtr<Element>>& ancestors, PassRefPtr<Element> blockToInsert); bool shouldUseDefaultParagraphElement(Node*) const; - virtual bool preservesTypingStyle() const; + bool preservesTypingStyle() const override; RefPtr<EditingStyle> m_style; @@ -59,6 +58,4 @@ private: bool m_pasteBlockqutoeIntoUnquotedArea; }; -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/editing/InsertTextCommand.cpp b/Source/WebCore/editing/InsertTextCommand.cpp index 6e645d88d..36c9f3892 100644 --- a/Source/WebCore/editing/InsertTextCommand.cpp +++ b/Source/WebCore/editing/InsertTextCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2005 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -27,9 +27,9 @@ #include "InsertTextCommand.h" #include "Document.h" -#include "Element.h" #include "Editor.h" #include "Frame.h" +#include "HTMLElement.h" #include "HTMLInterchange.h" #include "Text.h" #include "VisibleUnits.h" @@ -37,16 +37,16 @@ namespace WebCore { -InsertTextCommand::InsertTextCommand(Document& document, const String& text, bool selectInsertedText, RebalanceType rebalanceType) - : CompositeEditCommand(document) +InsertTextCommand::InsertTextCommand(Document& document, const String& text, bool selectInsertedText, RebalanceType rebalanceType, EditAction editingAction) + : CompositeEditCommand(document, editingAction) , m_text(text) , m_selectInsertedText(selectInsertedText) , m_rebalanceType(rebalanceType) { } -InsertTextCommand::InsertTextCommand(Document& document, const String& text, PassRefPtr<TextInsertionMarkerSupplier> markerSupplier) - : CompositeEditCommand(document) +InsertTextCommand::InsertTextCommand(Document& document, const String& text, PassRefPtr<TextInsertionMarkerSupplier> markerSupplier, EditAction editingAction) + : CompositeEditCommand(document, editingAction) , m_text(text) , m_selectInsertedText(false) , m_rebalanceType(RebalanceLeadingAndTrailingWhitespaces) @@ -58,7 +58,7 @@ Position InsertTextCommand::positionInsideTextNode(const Position& p) { Position pos = p; if (isTabSpanTextNode(pos.anchorNode())) { - RefPtr<Node> textNode = document().createEditingTextNode(""); + RefPtr<Node> textNode = document().createEditingTextNode(emptyString()); insertNodeAtTabSpanPosition(textNode.get(), pos); return firstPositionInNode(textNode.get()); } @@ -66,7 +66,7 @@ Position InsertTextCommand::positionInsideTextNode(const Position& p) // Prepare for text input by looking at the specified position. // It may be necessary to insert a text node to receive characters. if (!pos.containerNode()->isTextNode()) { - RefPtr<Node> textNode = document().createEditingTextNode(""); + RefPtr<Node> textNode = document().createEditingTextNode(emptyString()); insertNodeAt(textNode.get(), pos); return firstPositionInNode(textNode.get()); } @@ -120,7 +120,7 @@ bool InsertTextCommand::performOverwrite(const String& text, bool selectInserted replaceTextInNode(textNode, start.offsetInContainerNode(), count, text); - Position endPosition = Position(textNode.release(), start.offsetInContainerNode() + text.length()); + Position endPosition = Position(textNode.get(), start.offsetInContainerNode() + text.length()); setEndingSelectionWithoutValidation(start, endPosition); if (!selectInsertedText) setEndingSelection(VisibleSelection(endingSelection().visibleEnd(), endingSelection().isDirectional())); @@ -132,7 +132,7 @@ void InsertTextCommand::doApply() { ASSERT(m_text.find('\n') == notFound); - if (!endingSelection().isNonOrphanedCaretOrRange()) + if (endingSelection().isNoneOrOrphaned()) return; // Delete the current selection. @@ -176,7 +176,7 @@ void InsertTextCommand::doApply() // and so deleteInsignificantText could remove it. Save the position before the node in case that happens. Position positionBeforeStartNode(positionInParentBeforeNode(startPosition.containerNode())); deleteInsignificantText(startPosition.upstream(), startPosition.downstream()); - if (!startPosition.anchorNode()->inDocument()) + if (!startPosition.anchorNode()->isConnected()) startPosition = positionBeforeStartNode; if (!startPosition.isCandidate()) startPosition = startPosition.downstream(); @@ -202,7 +202,7 @@ void InsertTextCommand::doApply() const unsigned offset = startPosition.offsetInContainerNode(); insertTextIntoNode(textNode, offset, m_text); - endPosition = Position(textNode, offset + m_text.length()); + endPosition = Position(textNode.get(), offset + m_text.length()); if (m_markerSupplier) m_markerSupplier->addMarkersToTextNode(textNode.get(), offset, m_text); @@ -235,27 +235,29 @@ void InsertTextCommand::doApply() Position InsertTextCommand::insertTab(const Position& pos) { Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent(); + if (insertPos.isNull()) + return pos; Node* node = insertPos.containerNode(); unsigned int offset = node->isTextNode() ? insertPos.offsetInContainerNode() : 0; // keep tabs coalesced in tab span if (isTabSpanTextNode(node)) { - RefPtr<Text> textNode = toText(node); + RefPtr<Text> textNode = downcast<Text>(node); insertTextIntoNode(textNode, offset, "\t"); - return Position(textNode.release(), offset + 1); + return Position(textNode.get(), offset + 1); } // create new tab span - RefPtr<Element> spanNode = createTabSpanElement(document()); + auto spanNode = createTabSpanElement(document()); // place it - if (!node->isTextNode()) { - insertNodeAt(spanNode.get(), insertPos); - } else { - RefPtr<Text> textNode = toText(node); + if (!is<Text>(*node)) + insertNodeAt(spanNode.ptr(), insertPos); + else { + RefPtr<Text> textNode = downcast<Text>(node); if (offset >= textNode->length()) - insertNodeAfter(spanNode, textNode.release()); + insertNodeAfter(spanNode.copyRef(), WTFMove(textNode)); else { // split node to make room for the span // NOTE: splitTextNode uses textNode for the @@ -263,12 +265,12 @@ Position InsertTextCommand::insertTab(const Position& pos) // insert the span before it. if (offset > 0) splitTextNode(textNode, offset); - insertNodeBefore(spanNode, textNode.release()); + insertNodeBefore(spanNode.copyRef(), WTFMove(textNode)); } } // return the position following the new tab - return lastPositionInNode(spanNode.get()); + return lastPositionInNode(spanNode.ptr()); } } diff --git a/Source/WebCore/editing/InsertTextCommand.h b/Source/WebCore/editing/InsertTextCommand.h index 9cbff85be..bf7917bfb 100644 --- a/Source/WebCore/editing/InsertTextCommand.h +++ b/Source/WebCore/editing/InsertTextCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef InsertTextCommand_h -#define InsertTextCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -48,29 +47,28 @@ public: RebalanceAllWhitespaces }; - static PassRefPtr<InsertTextCommand> create(Document& document, const String& text, bool selectInsertedText = false, - RebalanceType rebalanceType = RebalanceLeadingAndTrailingWhitespaces) + static Ref<InsertTextCommand> create(Document& document, const String& text, bool selectInsertedText = false, + RebalanceType rebalanceType = RebalanceLeadingAndTrailingWhitespaces, EditAction editingAction = EditActionInsert) { - return adoptRef(new InsertTextCommand(document, text, selectInsertedText, rebalanceType)); + return adoptRef(*new InsertTextCommand(document, text, selectInsertedText, rebalanceType, editingAction)); } - static PassRefPtr<InsertTextCommand> createWithMarkerSupplier(Document& document, const String& text, PassRefPtr<TextInsertionMarkerSupplier> markerSupplier) + static Ref<InsertTextCommand> createWithMarkerSupplier(Document& document, const String& text, PassRefPtr<TextInsertionMarkerSupplier> markerSupplier, EditAction editingAction = EditActionInsert) { - return adoptRef(new InsertTextCommand(document, text, markerSupplier)); + return adoptRef(*new InsertTextCommand(document, text, markerSupplier, editingAction)); } -private: +protected: + InsertTextCommand(Document&, const String& text, PassRefPtr<TextInsertionMarkerSupplier>, EditAction); + InsertTextCommand(Document&, const String& text, bool selectInsertedText, RebalanceType, EditAction); - InsertTextCommand(Document&, const String& text, bool selectInsertedText, RebalanceType); - InsertTextCommand(Document&, const String& text, PassRefPtr<TextInsertionMarkerSupplier>); +private: void deleteCharacter(); - virtual void doApply(); + void doApply() override; -#if PLATFORM(IOS) - virtual bool isInsertTextCommand() const override { return true; } -#endif + bool isInsertTextCommand() const override { return true; } Position positionInsideTextNode(const Position&); Position insertTab(const Position&); @@ -88,5 +86,3 @@ private: }; } // namespace WebCore - -#endif // InsertTextCommand_h diff --git a/Source/WebCore/editing/MarkupAccumulator.cpp b/Source/WebCore/editing/MarkupAccumulator.cpp index 09b7f0717..d3bb0afac 100644 --- a/Source/WebCore/editing/MarkupAccumulator.cpp +++ b/Source/WebCore/editing/MarkupAccumulator.cpp @@ -40,64 +40,80 @@ #include "XLinkNames.h" #include "XMLNSNames.h" #include "XMLNames.h" +#include <wtf/NeverDestroyed.h> #include <wtf/unicode/CharacterNames.h> namespace WebCore { using namespace HTMLNames; +struct EntityDescription { + const char* characters; + unsigned char length; + unsigned char mask; +}; + +static const EntityDescription entitySubstitutionList[] = { + { "", 0 , 0 }, + { "&", 5 , EntityAmp }, + { "<", 4, EntityLt }, + { ">", 4, EntityGt }, + { """, 6, EntityQuot }, + { " ", 6, EntityNbsp }, +}; + +enum EntitySubstitutionIndex { + EntitySubstitutionNullIndex = 0, + EntitySubstitutionAmpIndex = 1, + EntitySubstitutionLtIndex = 2, + EntitySubstitutionGtIndex = 3, + EntitySubstitutionQuotIndex = 4, + EntitySubstitutionNbspIndex = 5, +}; + +static const unsigned maximumEscapedentityCharacter = noBreakSpace; +static const uint8_t entityMap[maximumEscapedentityCharacter + 1] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + EntitySubstitutionQuotIndex, // '"'. + 0, 0, 0, + EntitySubstitutionAmpIndex, // '&'. + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + EntitySubstitutionLtIndex, // '<'. + 0, + EntitySubstitutionGtIndex, // '>'. + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + EntitySubstitutionNbspIndex // noBreakSpace. +}; + +template<typename CharacterType> +static inline void appendCharactersReplacingEntitiesInternal(StringBuilder& result, const String& source, unsigned offset, unsigned length, EntityMask entityMask) +{ + const CharacterType* text = source.characters<CharacterType>() + offset; + + size_t positionAfterLastEntity = 0; + for (size_t i = 0; i < length; ++i) { + CharacterType character = text[i]; + uint8_t substitution = character < WTF_ARRAY_LENGTH(entityMap) ? entityMap[character] : static_cast<uint8_t>(EntitySubstitutionNullIndex); + if (UNLIKELY(substitution != EntitySubstitutionNullIndex) && entitySubstitutionList[substitution].mask & entityMask) { + result.append(text + positionAfterLastEntity, i - positionAfterLastEntity); + result.append(entitySubstitutionList[substitution].characters, entitySubstitutionList[substitution].length); + positionAfterLastEntity = i + 1; + } + } + result.append(text + positionAfterLastEntity, length - positionAfterLastEntity); +} + void MarkupAccumulator::appendCharactersReplacingEntities(StringBuilder& result, const String& source, unsigned offset, unsigned length, EntityMask entityMask) { - DEFINE_STATIC_LOCAL(const String, ampReference, (ASCIILiteral("&"))); - DEFINE_STATIC_LOCAL(const String, ltReference, (ASCIILiteral("<"))); - DEFINE_STATIC_LOCAL(const String, gtReference, (ASCIILiteral(">"))); - DEFINE_STATIC_LOCAL(const String, quotReference, (ASCIILiteral("""))); - DEFINE_STATIC_LOCAL(const String, nbspReference, (ASCIILiteral(" "))); - - static const EntityDescription entityMaps[] = { - { '&', ampReference, EntityAmp }, - { '<', ltReference, EntityLt }, - { '>', gtReference, EntityGt }, - { '"', quotReference, EntityQuot }, - { noBreakSpace, nbspReference, EntityNbsp }, - }; - if (!(offset + length)) return; ASSERT(offset + length <= source.length()); - if (source.is8Bit()) { - const LChar* text = source.characters8() + offset; - - size_t positionAfterLastEntity = 0; - for (size_t i = 0; i < length; ++i) { - for (size_t entityIndex = 0; entityIndex < WTF_ARRAY_LENGTH(entityMaps); ++entityIndex) { - if (text[i] == entityMaps[entityIndex].entity && entityMaps[entityIndex].mask & entityMask) { - result.append(text + positionAfterLastEntity, i - positionAfterLastEntity); - result.append(entityMaps[entityIndex].reference); - positionAfterLastEntity = i + 1; - break; - } - } - } - result.append(text + positionAfterLastEntity, length - positionAfterLastEntity); - } else { - const UChar* text = source.characters16() + offset; - - size_t positionAfterLastEntity = 0; - for (size_t i = 0; i < length; ++i) { - for (size_t entityIndex = 0; entityIndex < WTF_ARRAY_LENGTH(entityMaps); ++entityIndex) { - if (text[i] == entityMaps[entityIndex].entity && entityMaps[entityIndex].mask & entityMask) { - result.append(text + positionAfterLastEntity, i - positionAfterLastEntity); - result.append(entityMaps[entityIndex].reference); - positionAfterLastEntity = i + 1; - break; - } - } - } - result.append(text + positionAfterLastEntity, length - positionAfterLastEntity); - } + if (source.is8Bit()) + appendCharactersReplacingEntitiesInternal<LChar>(result, source, offset, length, entityMask); + else + appendCharactersReplacingEntitiesInternal<UChar>(result, source, offset, length, entityMask); } MarkupAccumulator::MarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs resolveUrlsMethod, const Range* range, EFragmentSerialization fragmentSerialization) @@ -113,20 +129,17 @@ MarkupAccumulator::~MarkupAccumulator() { } -String MarkupAccumulator::serializeNodes(Node& targetNode, Node* nodeToSkip, EChildrenOnly childrenOnly, Vector<QualifiedName>* tagNamesToSkip) +String MarkupAccumulator::serializeNodes(Node& targetNode, EChildrenOnly childrenOnly, Vector<QualifiedName>* tagNamesToSkip) { - serializeNodesWithNamespaces(targetNode, nodeToSkip, childrenOnly, 0, tagNamesToSkip); + serializeNodesWithNamespaces(targetNode, childrenOnly, 0, tagNamesToSkip); return m_markup.toString(); } -void MarkupAccumulator::serializeNodesWithNamespaces(Node& targetNode, Node* nodeToSkip, EChildrenOnly childrenOnly, const Namespaces* namespaces, Vector<QualifiedName>* tagNamesToSkip) +void MarkupAccumulator::serializeNodesWithNamespaces(Node& targetNode, EChildrenOnly childrenOnly, const Namespaces* namespaces, Vector<QualifiedName>* tagNamesToSkip) { - if (&targetNode == nodeToSkip) - return; - - if (tagNamesToSkip && targetNode.isElementNode()) { - for (size_t i = 0; i < tagNamesToSkip->size(); ++i) { - if (targetNode.hasTagName(tagNamesToSkip->at(i))) + if (tagNamesToSkip && is<Element>(targetNode)) { + for (auto& name : *tagNamesToSkip) { + if (downcast<Element>(targetNode).hasTagName(name)) return; } } @@ -143,15 +156,12 @@ void MarkupAccumulator::serializeNodesWithNamespaces(Node& targetNode, Node* nod if (!childrenOnly) appendStartTag(targetNode, &namespaceHash); - if (!(targetNode.document().isHTMLDocument() && elementCannotHaveEndTag(targetNode))) { -#if ENABLE(TEMPLATE_ELEMENT) - Node* current = targetNode.hasTagName(templateTag) ? toHTMLTemplateElement(targetNode).content()->firstChild() : targetNode.firstChild(); -#else - Node* current = targetNode.firstChild(); -#endif - for ( ; current; current = current->nextSibling()) - serializeNodesWithNamespaces(*current, nodeToSkip, IncludeNode, &namespaceHash, tagNamesToSkip); - } + if (targetNode.document().isHTMLDocument() && elementCannotHaveEndTag(targetNode)) + return; + + Node* current = targetNode.hasTagName(templateTag) ? downcast<HTMLTemplateElement>(targetNode).content().firstChild() : targetNode.firstChild(); + for ( ; current; current = current->nextSibling()) + serializeNodesWithNamespaces(*current, IncludeNode, &namespaceHash, tagNamesToSkip); if (!childrenOnly) appendEndTag(targetNode); @@ -186,16 +196,16 @@ void MarkupAccumulator::appendStartTag(const Node& node, Namespaces* namespaces) m_nodes->append(const_cast<Node*>(&node)); } -void MarkupAccumulator::appendEndTag(const Node& node) +void MarkupAccumulator::appendEndTag(const Element& element) { - appendEndMarkup(m_markup, node); + appendEndMarkup(m_markup, element); } size_t MarkupAccumulator::totalLength(const Vector<String>& strings) { size_t length = 0; - for (size_t i = 0; i < strings.size(); ++i) - length += strings[i].length(); + for (auto& string : strings) + length += string.length(); return length; } @@ -204,10 +214,10 @@ void MarkupAccumulator::concatenateMarkup(StringBuilder& result) result.append(m_markup); } -void MarkupAccumulator::appendAttributeValue(StringBuilder& result, const String& attribute, bool documentIsHTML) +void MarkupAccumulator::appendAttributeValue(StringBuilder& result, const String& attribute, bool isSerializingHTML) { appendCharactersReplacingEntities(result, attribute, 0, attribute.length(), - documentIsHTML ? EntityMaskInHTMLAttributeValue : EntityMaskInAttributeValue); + isSerializingHTML ? EntityMaskInHTMLAttributeValue : EntityMaskInAttributeValue); } void MarkupAccumulator::appendCustomAttributes(StringBuilder&, const Element&, Namespaces*) @@ -240,24 +250,6 @@ void MarkupAccumulator::appendQuotedURLAttributeValue(StringBuilder& result, con result.append(quoteChar); } -void MarkupAccumulator::appendNodeValue(StringBuilder& result, const Node& node, const Range* range, EntityMask entityMask) -{ - const String str = node.nodeValue(); - unsigned length = str.length(); - unsigned start = 0; - - if (range) { - if (&node == range->endContainer()) - length = range->endOffset(); - if (&node == range->startContainer()) { - start = range->startOffset(); - length -= start; - } - } - - appendCharactersReplacingEntities(result, str, start, length, entityMask); -} - bool MarkupAccumulator::shouldAddNamespaceElement(const Element& element) { // Don't add namespace attribute if it is already defined for this elem. @@ -265,8 +257,8 @@ bool MarkupAccumulator::shouldAddNamespaceElement(const Element& element) if (prefix.isEmpty()) return !element.hasAttribute(xmlnsAtom); - DEFINE_STATIC_LOCAL(String, xmlnsWithColon, (ASCIILiteral("xmlns:"))); - return !element.hasAttribute(xmlnsWithColon + prefix); + static NeverDestroyed<String> xmlnsWithColon(ASCIILiteral("xmlns:")); + return !element.hasAttribute(xmlnsWithColon.get() + prefix); } bool MarkupAccumulator::shouldAddNamespaceAttribute(const Attribute& attribute, Namespaces& namespaces) @@ -333,7 +325,7 @@ EntityMask MarkupAccumulator::entityMaskForText(const Text& text) const if (!text.document().isHTMLDocument()) return EntityMaskInPCDATA; - const QualifiedName* parentName = 0; + const QualifiedName* parentName = nullptr; if (text.parentElement()) parentName = &text.parentElement()->tagQName(); @@ -344,7 +336,20 @@ EntityMask MarkupAccumulator::entityMaskForText(const Text& text) const void MarkupAccumulator::appendText(StringBuilder& result, const Text& text) { - appendNodeValue(result, text, m_range, entityMaskForText(text)); + const String& textData = text.data(); + unsigned start = 0; + unsigned length = textData.length(); + + if (m_range) { + if (&text == &m_range->endContainer()) + length = m_range->endOffset(); + if (&text == &m_range->startContainer()) { + start = m_range->startOffset(); + length -= start; + } + } + + appendCharactersReplacingEntities(result, textData, start, length, entityMaskForText(text)); } static void appendComment(StringBuilder& result, const String& comment) @@ -400,12 +405,6 @@ void MarkupAccumulator::appendDocumentType(StringBuilder& result, const Document result.append(documentType.systemId()); result.append('"'); } - if (!documentType.internalSubset().isEmpty()) { - result.append(' '); - result.append('['); - result.append(documentType.internalSubset()); - result.append(']'); - } result.append('>'); } @@ -478,7 +477,7 @@ void MarkupAccumulator::generateUniquePrefix(QualifiedName& prefixedName, const StringBuilder builder; do { builder.clear(); - builder.append("NS"); + builder.appendLiteral("NS"); builder.appendNumber(++m_prefixLevel); const AtomicString& name = builder.toAtomicString(); if (!namespaces.get(name.impl())) { @@ -490,24 +489,29 @@ void MarkupAccumulator::generateUniquePrefix(QualifiedName& prefixedName, const void MarkupAccumulator::appendAttribute(StringBuilder& result, const Element& element, const Attribute& attribute, Namespaces* namespaces) { - bool documentIsHTML = element.document().isHTMLDocument(); + bool isSerializingHTML = element.document().isHTMLDocument() && !inXMLFragmentSerialization(); result.append(' '); QualifiedName prefixedName = attribute.name(); - if (documentIsHTML && !attributeIsInSerializedNamespace(attribute)) + if (isSerializingHTML && !attributeIsInSerializedNamespace(attribute)) result.append(attribute.name().localName()); else { if (!attribute.namespaceURI().isEmpty()) { - AtomicStringImpl* foundNS = namespaces && attribute.prefix().impl() ? namespaces->get(attribute.prefix().impl()) : 0; - bool prefixIsAlreadyMappedToOtherNS = foundNS && foundNS != attribute.namespaceURI().impl(); - if (attribute.prefix().isEmpty() || !foundNS || prefixIsAlreadyMappedToOtherNS) { - if (AtomicStringImpl* prefix = namespaces ? namespaces->get(attribute.namespaceURI().impl()) : 0) - prefixedName.setPrefix(AtomicString(prefix)); - else { - bool shouldBeDeclaredUsingAppendNamespace = !attribute.prefix().isEmpty() && !foundNS; - if (!shouldBeDeclaredUsingAppendNamespace && attribute.localName() != xmlnsAtom && namespaces) - generateUniquePrefix(prefixedName, *namespaces); + if (attribute.namespaceURI() == XMLNames::xmlNamespaceURI) { + // Always use xml as prefix if the namespace is the XML namespace. + prefixedName.setPrefix(xmlAtom); + } else { + AtomicStringImpl* foundNS = namespaces && attribute.prefix().impl() ? namespaces->get(attribute.prefix().impl()) : 0; + bool prefixIsAlreadyMappedToOtherNS = foundNS && foundNS != attribute.namespaceURI().impl(); + if (attribute.prefix().isEmpty() || !foundNS || prefixIsAlreadyMappedToOtherNS) { + if (AtomicStringImpl* prefix = namespaces ? namespaces->get(attribute.namespaceURI().impl()) : 0) + prefixedName.setPrefix(AtomicString(prefix)); + else { + bool shouldBeDeclaredUsingAppendNamespace = !attribute.prefix().isEmpty() && !foundNS; + if (!shouldBeDeclaredUsingAppendNamespace && attribute.localName() != xmlnsAtom && namespaces) + generateUniquePrefix(prefixedName, *namespaces); + } } } } @@ -520,11 +524,11 @@ void MarkupAccumulator::appendAttribute(StringBuilder& result, const Element& el appendQuotedURLAttributeValue(result, element, attribute); else { result.append('"'); - appendAttributeValue(result, attribute.value(), documentIsHTML); + appendAttributeValue(result, attribute.value(), isSerializingHTML); result.append('"'); } - if ((inXMLFragmentSerialization() || !documentIsHTML) && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces)) + if (!isSerializingHTML && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces)) appendNamespace(result, prefixedName.prefix(), prefixedName.namespaceURI(), *namespaces); } @@ -543,33 +547,29 @@ void MarkupAccumulator::appendStartMarkup(StringBuilder& result, const Node& nod switch (node.nodeType()) { case Node::TEXT_NODE: - appendText(result, toText(node)); + appendText(result, downcast<Text>(node)); break; case Node::COMMENT_NODE: - appendComment(result, toComment(node).data()); + appendComment(result, downcast<Comment>(node).data()); break; case Node::DOCUMENT_NODE: - appendXMLDeclaration(result, toDocument(node)); + appendXMLDeclaration(result, downcast<Document>(node)); break; case Node::DOCUMENT_FRAGMENT_NODE: break; case Node::DOCUMENT_TYPE_NODE: - appendDocumentType(result, toDocumentType(node)); + appendDocumentType(result, downcast<DocumentType>(node)); break; case Node::PROCESSING_INSTRUCTION_NODE: - appendProcessingInstruction(result, toProcessingInstruction(node).target(), toProcessingInstruction(node).data()); + appendProcessingInstruction(result, downcast<ProcessingInstruction>(node).target(), downcast<ProcessingInstruction>(node).data()); break; case Node::ELEMENT_NODE: - appendElement(result, toElement(node), namespaces); + appendElement(result, downcast<Element>(node), namespaces); break; case Node::CDATA_SECTION_NODE: - appendCDATASection(result, toCDATASection(node).data()); + appendCDATASection(result, downcast<CDATASection>(node).data()); break; case Node::ATTRIBUTE_NODE: - case Node::ENTITY_NODE: - case Node::ENTITY_REFERENCE_NODE: - case Node::NOTATION_NODE: - case Node::XPATH_NAMESPACE_NODE: ASSERT_NOT_REACHED(); break; } @@ -580,37 +580,44 @@ void MarkupAccumulator::appendStartMarkup(StringBuilder& result, const Node& nod // 2. Elements w/ children never self-close because they use a separate end tag. // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag. // 4. Other elements self-close. -bool MarkupAccumulator::shouldSelfClose(const Node& node) +bool MarkupAccumulator::shouldSelfClose(const Element& element) { - if (!inXMLFragmentSerialization() && node.document().isHTMLDocument()) + if (!inXMLFragmentSerialization() && element.document().isHTMLDocument()) return false; - if (node.hasChildNodes()) + if (element.hasChildNodes()) return false; - if (node.isHTMLElement() && !elementCannotHaveEndTag(node)) + if (element.isHTMLElement() && !elementCannotHaveEndTag(element)) return false; return true; } bool MarkupAccumulator::elementCannotHaveEndTag(const Node& node) { - if (!node.isHTMLElement()) + if (!is<HTMLElement>(node)) 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 - // which elements should be serialized w/o end tags. - return toHTMLElement(node).ieForbidsInsertHTML(); + // From https://html.spec.whatwg.org/#serialising-html-fragments: + // If current node is an area, base, basefont, bgsound, br, col, embed, frame, hr, img, + // input, keygen, link, meta, param, source, track or wbr element, then continue on to + // the next child node at this point. + static const HTMLQualifiedName* tags[] = { &areaTag, &baseTag, &basefontTag, &bgsoundTag, &brTag, &colTag, &embedTag, + &frameTag, &hrTag, &imgTag, &inputTag, &keygenTag, &linkTag, &metaTag, ¶mTag, &sourceTag, &trackTag, &wbrTag }; + auto& element = downcast<HTMLElement>(node); + for (auto* tag : tags) { + if (element.hasTagName(*tag)) + return true; + } + return false; } -void MarkupAccumulator::appendEndMarkup(StringBuilder& result, const Node& node) +void MarkupAccumulator::appendEndMarkup(StringBuilder& result, const Element& element) { - if (!node.isElementNode() || shouldSelfClose(node) || (!node.hasChildNodes() && elementCannotHaveEndTag(node))) + if (shouldSelfClose(element) || (!element.hasChildNodes() && elementCannotHaveEndTag(element))) return; result.append('<'); result.append('/'); - result.append(toElement(node).nodeNamePreservingCase()); + result.append(element.nodeNamePreservingCase()); result.append('>'); } diff --git a/Source/WebCore/editing/MarkupAccumulator.h b/Source/WebCore/editing/MarkupAccumulator.h index 842337e14..38b73c62b 100644 --- a/Source/WebCore/editing/MarkupAccumulator.h +++ b/Source/WebCore/editing/MarkupAccumulator.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,9 +23,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MarkupAccumulator_h -#define MarkupAccumulator_h +#pragma once +#include "Element.h" #include "markup.h" #include <wtf/HashMap.h> #include <wtf/text/StringBuilder.h> @@ -53,22 +53,16 @@ enum EntityMask { EntityMaskInPCDATA = EntityAmp | EntityLt | EntityGt, EntityMaskInHTMLPCDATA = EntityMaskInPCDATA | EntityNbsp, EntityMaskInAttributeValue = EntityAmp | EntityLt | EntityGt | EntityQuot, - EntityMaskInHTMLAttributeValue = EntityMaskInAttributeValue | EntityNbsp, -}; - -struct EntityDescription { - UChar entity; - const String& reference; - EntityMask mask; + EntityMaskInHTMLAttributeValue = EntityAmp | EntityQuot | EntityNbsp, }; // FIXME: Noncopyable? class MarkupAccumulator { public: - MarkupAccumulator(Vector<Node*>*, EAbsoluteURLs, const Range* = 0, EFragmentSerialization = HTMLFragmentSerialization); + MarkupAccumulator(Vector<Node*>*, EAbsoluteURLs, const Range* = nullptr, EFragmentSerialization = HTMLFragmentSerialization); virtual ~MarkupAccumulator(); - String serializeNodes(Node& targetNode, Node* nodeToSkip, EChildrenOnly, Vector<QualifiedName>* tagNamesToSkip = nullptr); + String serializeNodes(Node& targetNode, EChildrenOnly, Vector<QualifiedName>* tagNamesToSkip = nullptr); static void appendCharactersReplacingEntities(StringBuilder&, const String&, unsigned, unsigned, EntityMask); @@ -78,22 +72,27 @@ protected: void concatenateMarkup(StringBuilder&); - virtual void appendString(const String&); - virtual void appendEndTag(const Node&); + void appendString(const String&); + void appendEndTag(const Node& node) + { + if (is<Element>(node)) + appendEndTag(downcast<Element>(node)); + } + + virtual void appendEndTag(const Element&); virtual void appendCustomAttributes(StringBuilder&, const Element&, Namespaces*); virtual void appendText(StringBuilder&, const Text&); virtual void appendElement(StringBuilder&, const Element&, Namespaces*); - void appendStartTag(const Node&, Namespaces* = 0); + void appendStartTag(const Node&, Namespaces* = nullptr); void appendOpenTag(StringBuilder&, const Element&, Namespaces*); void appendCloseTag(StringBuilder&, const Element&); void appendStartMarkup(StringBuilder&, const Node&, Namespaces*); - void appendEndMarkup(StringBuilder&, const Node&); + void appendEndMarkup(StringBuilder&, const Element&); - void appendAttributeValue(StringBuilder&, const String&, bool); - void appendNodeValue(StringBuilder&, const Node&, const Range*, EntityMask); + void appendAttributeValue(StringBuilder&, const String&, bool isSerializingHTML); void appendNamespace(StringBuilder&, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces&, bool allowEmptyDefaultNS = false); void appendXMLDeclaration(StringBuilder&, const Document&); void appendDocumentType(StringBuilder&, const DocumentType&); @@ -103,7 +102,7 @@ protected: bool shouldAddNamespaceElement(const Element&); bool shouldAddNamespaceAttribute(const Attribute&, Namespaces&); - bool shouldSelfClose(const Node&); + bool shouldSelfClose(const Element&); bool elementCannotHaveEndTag(const Node&); EntityMask entityMaskForText(const Text&) const; @@ -113,7 +112,7 @@ protected: private: String resolveURLIfNeeded(const Element&, const String&) const; void appendQuotedURLAttributeValue(StringBuilder&, const Element&, const Attribute&); - void serializeNodesWithNamespaces(Node& targetNode, Node* nodeToSkip, EChildrenOnly, const Namespaces*, Vector<QualifiedName>* tagNamesToSkip); + void serializeNodesWithNamespaces(Node& targetNode, EChildrenOnly, const Namespaces*, Vector<QualifiedName>* tagNamesToSkip); bool inXMLFragmentSerialization() const { return m_fragmentSerialization == XMLFragmentSerialization; } void generateUniquePrefix(QualifiedName&, const Namespaces&); @@ -123,6 +122,4 @@ private: unsigned m_prefixLevel; }; -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/editing/MergeIdenticalElementsCommand.cpp b/Source/WebCore/editing/MergeIdenticalElementsCommand.cpp index 1e639eb6d..76c376f51 100644 --- a/Source/WebCore/editing/MergeIdenticalElementsCommand.cpp +++ b/Source/WebCore/editing/MergeIdenticalElementsCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -47,15 +47,14 @@ void MergeIdenticalElementsCommand::doApply() m_atChild = m_element2->firstChild(); - Vector<RefPtr<Node>> children; + Vector<Ref<Node>> children; for (Node* child = m_element1->firstChild(); child; child = child->nextSibling()) - children.append(child); + children.append(*child); - size_t size = children.size(); - for (size_t i = 0; i < size; ++i) - m_element2->insertBefore(children[i].release(), m_atChild.get(), IGNORE_EXCEPTION); + for (auto& child : children) + m_element2->insertBefore(child, m_atChild.get()); - m_element1->remove(IGNORE_EXCEPTION); + m_element1->remove(); } void MergeIdenticalElementsCommand::doUnapply() @@ -63,25 +62,21 @@ void MergeIdenticalElementsCommand::doUnapply() ASSERT(m_element1); ASSERT(m_element2); - RefPtr<Node> atChild = m_atChild.release(); + RefPtr<Node> atChild = WTFMove(m_atChild); - ContainerNode* parent = m_element2->parentNode(); + auto* parent = m_element2->parentNode(); if (!parent || !parent->hasEditableStyle()) return; - ExceptionCode ec = 0; - - parent->insertBefore(m_element1.get(), m_element2.get(), ec); - if (ec) + if (parent->insertBefore(*m_element1, m_element2.get()).hasException()) return; - Vector<RefPtr<Node>> children; + Vector<Ref<Node>> children; for (Node* child = m_element2->firstChild(); child && child != atChild; child = child->nextSibling()) - children.append(child); + children.append(*child); - size_t size = children.size(); - for (size_t i = 0; i < size; ++i) - m_element1->appendChild(children[i].release(), ec); + for (auto& child : children) + m_element1->appendChild(child); } #ifndef NDEBUG diff --git a/Source/WebCore/editing/MergeIdenticalElementsCommand.h b/Source/WebCore/editing/MergeIdenticalElementsCommand.h index cc9ddf534..d5972a17d 100644 --- a/Source/WebCore/editing/MergeIdenticalElementsCommand.h +++ b/Source/WebCore/editing/MergeIdenticalElementsCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MergeIdenticalElementsCommand_h -#define MergeIdenticalElementsCommand_h +#pragma once #include "EditCommand.h" @@ -32,19 +31,19 @@ namespace WebCore { class MergeIdenticalElementsCommand : public SimpleEditCommand { public: - static PassRefPtr<MergeIdenticalElementsCommand> create(PassRefPtr<Element> element1, PassRefPtr<Element> element2) + static Ref<MergeIdenticalElementsCommand> create(PassRefPtr<Element> element1, PassRefPtr<Element> element2) { - return adoptRef(new MergeIdenticalElementsCommand(element1, element2)); + return adoptRef(*new MergeIdenticalElementsCommand(element1, element2)); } private: MergeIdenticalElementsCommand(PassRefPtr<Element>, PassRefPtr<Element>); - virtual void doApply() override; - virtual void doUnapply() override; + void doApply() override; + void doUnapply() override; #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override; + void getNodesInCommand(HashSet<Node*>&) override; #endif RefPtr<Element> m_element1; @@ -53,5 +52,3 @@ private: }; } // namespace WebCore - -#endif // MergeIdenticalElementsCommand_h diff --git a/Source/WebCore/editing/ModifySelectionListLevel.cpp b/Source/WebCore/editing/ModifySelectionListLevel.cpp index 54ae258e4..f3400fda6 100644 --- a/Source/WebCore/editing/ModifySelectionListLevel.cpp +++ b/Source/WebCore/editing/ModifySelectionListLevel.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -29,7 +29,8 @@ #include "Document.h" #include "Frame.h" #include "FrameSelection.h" -#include "HTMLElement.h" +#include "HTMLOListElement.h" +#include "HTMLUListElement.h" #include "RenderObject.h" #include "htmlediting.h" @@ -79,7 +80,7 @@ static bool getStartEndListChildren(const VisibleSelection& selection, Node*& st // if the selection ends on a list item with a sublist, include the entire sublist if (endListChild->renderer()->isListItem()) { RenderObject* r = endListChild->renderer()->nextSibling(); - if (r && isListElement(r->node())) + if (r && isListHTMLElement(r->node())) endListChild = r->node(); } @@ -176,9 +177,9 @@ void IncreaseSelectionListLevelCommand::doApply() return; Node* previousItem = startListChild->renderer()->previousSibling()->node(); - if (isListElement(previousItem)) { + if (isListHTMLElement(previousItem)) { // move nodes up into preceding list - appendSiblingNodeRange(startListChild, endListChild, toElement(previousItem)); + appendSiblingNodeRange(startListChild, endListChild, downcast<Element>(previousItem)); m_listElement = previousItem; } else { // create a sublist for the preceding element and move nodes there @@ -187,18 +188,18 @@ void IncreaseSelectionListLevelCommand::doApply() case InheritedListType: newParent = startListChild->parentElement(); if (newParent) - newParent = newParent->cloneElementWithoutChildren(); + newParent = newParent->cloneElementWithoutChildren(document()); break; case OrderedList: - newParent = createOrderedListElement(document()); + newParent = HTMLOListElement::create(document()); break; case UnorderedList: - newParent = createUnorderedListElement(document()); + newParent = HTMLUListElement::create(document()); break; } insertNodeBefore(newParent, startListChild); appendSiblingNodeRange(startListChild, endListChild, newParent.get()); - m_listElement = newParent.release(); + m_listElement = WTFMove(newParent); } } @@ -209,13 +210,13 @@ bool IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(Document* return canIncreaseListLevel(document->frame()->selection().selection(), startListChild, endListChild); } -PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document, Type type) +RefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document, Type type) { ASSERT(document); ASSERT(document->frame()); - RefPtr<IncreaseSelectionListLevelCommand> command = create(*document, type); + auto command = create(*document, type); command->apply(); - return command->m_listElement.release(); + return WTFMove(command->m_listElement); } PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document) @@ -243,11 +244,11 @@ static bool canDecreaseListLevel(const VisibleSelection& selection, Node*& start { if (!getStartEndListChildren(selection, start, end)) return false; - + // there must be a destination list to move the items to - if (!isListElement(start->parentNode()->parentNode())) + if (!isListHTMLElement(start->parentNode()->parentNode())) return false; - + return true; } diff --git a/Source/WebCore/editing/ModifySelectionListLevel.h b/Source/WebCore/editing/ModifySelectionListLevel.h index 061f3e587..cc06cf4de 100644 --- a/Source/WebCore/editing/ModifySelectionListLevel.h +++ b/Source/WebCore/editing/ModifySelectionListLevel.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ModifySelectionListLevel_h -#define ModifySelectionListLevel_h +#pragma once #include "CompositeEditCommand.h" @@ -42,7 +41,7 @@ protected: void insertSiblingNodeRangeAfter(Node* startNode, Node* endNode, Node* refNode); private: - virtual bool preservesTypingStyle() const; + bool preservesTypingStyle() const override; }; // IncreaseSelectionListLevelCommand moves the selected list items one level deeper. @@ -55,16 +54,16 @@ public: private: enum Type { InheritedListType, OrderedList, UnorderedList }; - static PassRefPtr<Node> increaseSelectionListLevel(Document*, Type); + static RefPtr<Node> increaseSelectionListLevel(Document*, Type); - static PassRefPtr<IncreaseSelectionListLevelCommand> create(Document& document, Type type) + static Ref<IncreaseSelectionListLevelCommand> create(Document& document, Type type) { - return adoptRef(new IncreaseSelectionListLevelCommand(document, type)); + return adoptRef(*new IncreaseSelectionListLevelCommand(document, type)); } IncreaseSelectionListLevelCommand(Document&, Type); - virtual void doApply(); + void doApply() override; Type m_listType; RefPtr<Node> m_listElement; @@ -77,16 +76,14 @@ public: static void decreaseSelectionListLevel(Document*); private: - static PassRefPtr<DecreaseSelectionListLevelCommand> create(Document& document) + static Ref<DecreaseSelectionListLevelCommand> create(Document& document) { - return adoptRef(new DecreaseSelectionListLevelCommand(document)); + return adoptRef(*new DecreaseSelectionListLevelCommand(document)); } explicit DecreaseSelectionListLevelCommand(Document&); - virtual void doApply(); + void doApply() override; }; } // namespace WebCore - -#endif // ModifySelectionListLevel_h diff --git a/Source/WebCore/editing/MoveSelectionCommand.cpp b/Source/WebCore/editing/MoveSelectionCommand.cpp index 7580b7d37..db910caf1 100644 --- a/Source/WebCore/editing/MoveSelectionCommand.cpp +++ b/Source/WebCore/editing/MoveSelectionCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2005, 2006 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -26,6 +26,7 @@ #include "config.h" #include "MoveSelectionCommand.h" +#include "DeleteSelectionCommand.h" #include "DocumentFragment.h" #include "ReplaceSelectionCommand.h" @@ -60,31 +61,43 @@ void MoveSelectionCommand::doApply() pos.moveToOffset(pos.offsetInContainerNode() + selectionStart.offsetInContainerNode()); } - deleteSelection(m_smartDelete); + { + auto deleteSelection = DeleteSelectionCommand::create(document(), m_smartDelete, true, false, true, true, EditActionDeleteByDrag); + deleteSelection->setParent(this); + deleteSelection->apply(); + m_commands.append(WTFMove(deleteSelection)); + } // If the node for the destination has been removed as a result of the deletion, // set the destination to the ending point after the deletion. // Fixes: <rdar://problem/3910425> REGRESSION (Mail): Crash in ReplaceSelectionCommand; // selection is empty, leading to null deref - if (!pos.anchorNode()->inDocument()) + if (!pos.anchorNode()->isConnected()) pos = endingSelection().start(); cleanupAfterDeletion(pos); setEndingSelection(VisibleSelection(pos, endingSelection().affinity(), endingSelection().isDirectional())); - if (!pos.anchorNode()->inDocument()) { + setStartingSelection(endingSelection()); + if (!pos.anchorNode()->isConnected()) { // Document was modified out from under us. return; } ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::PreventNesting; if (m_smartInsert) options |= ReplaceSelectionCommand::SmartReplace; - applyCommandToComposite(ReplaceSelectionCommand::create(document(), m_fragment, options)); + + { + auto replaceSelection = ReplaceSelectionCommand::create(document(), WTFMove(m_fragment), options, EditActionInsertFromDrop); + replaceSelection->setParent(this); + replaceSelection->apply(); + m_commands.append(WTFMove(replaceSelection)); + } } EditAction MoveSelectionCommand::editingAction() const { - return EditActionDrag; + return EditActionDeleteByDrag; } } // namespace WebCore diff --git a/Source/WebCore/editing/MoveSelectionCommand.h b/Source/WebCore/editing/MoveSelectionCommand.h index 6780caa42..8b3c338b1 100644 --- a/Source/WebCore/editing/MoveSelectionCommand.h +++ b/Source/WebCore/editing/MoveSelectionCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MoveSelectionCommand_h -#define MoveSelectionCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -34,16 +33,17 @@ class DocumentFragment; class MoveSelectionCommand : public CompositeEditCommand { public: - static PassRefPtr<MoveSelectionCommand> create(PassRefPtr<DocumentFragment> fragment, const Position& position, bool smartInsert = false, bool smartDelete = false) + static Ref<MoveSelectionCommand> create(PassRefPtr<DocumentFragment> fragment, const Position& position, bool smartInsert = false, bool smartDelete = false) { - return adoptRef(new MoveSelectionCommand(fragment, position, smartInsert, smartDelete)); + return adoptRef(*new MoveSelectionCommand(fragment, position, smartInsert, smartDelete)); } private: MoveSelectionCommand(PassRefPtr<DocumentFragment>, const Position&, bool smartInsert, bool smartDelete); - virtual void doApply(); - virtual EditAction editingAction() const; + void doApply() override; + EditAction editingAction() const override; + bool shouldDispatchInputEvents() const final { return false; } RefPtr<DocumentFragment> m_fragment; Position m_position; @@ -52,5 +52,3 @@ private: }; } // namespace WebCore - -#endif // MoveSelectionCommand_h diff --git a/Source/WebCore/editing/RemoveCSSPropertyCommand.cpp b/Source/WebCore/editing/RemoveCSSPropertyCommand.cpp index b22430bdb..3770565b9 100644 --- a/Source/WebCore/editing/RemoveCSSPropertyCommand.cpp +++ b/Source/WebCore/editing/RemoveCSSPropertyCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -27,7 +27,6 @@ #include "RemoveCSSPropertyCommand.h" #include "CSSStyleDeclaration.h" -#include "ExceptionCodePlaceholder.h" #include "StyleProperties.h" #include "StyledElement.h" #include <wtf/Assertions.h> @@ -55,12 +54,12 @@ void RemoveCSSPropertyCommand::doApply() // Mutate using the CSSOM wrapper so we get the same event behavior as a script. // Setting to null string removes the property. We don't have internal version of removeProperty. - m_element->style()->setPropertyInternal(m_property, String(), false, IGNORE_EXCEPTION); + m_element->cssomStyle()->setPropertyInternal(m_property, String(), false); } void RemoveCSSPropertyCommand::doUnapply() { - m_element->style()->setPropertyInternal(m_property, m_oldValue, m_important, IGNORE_EXCEPTION); + m_element->cssomStyle()->setPropertyInternal(m_property, m_oldValue, m_important); } #ifndef NDEBUG diff --git a/Source/WebCore/editing/RemoveCSSPropertyCommand.h b/Source/WebCore/editing/RemoveCSSPropertyCommand.h index abdab1531..185ebf5c6 100644 --- a/Source/WebCore/editing/RemoveCSSPropertyCommand.h +++ b/Source/WebCore/editing/RemoveCSSPropertyCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RemoveCSSPropertyCommand_h -#define RemoveCSSPropertyCommand_h +#pragma once #include "EditCommand.h" #include "CSSPropertyNames.h" @@ -35,20 +34,20 @@ class StyledElement; class RemoveCSSPropertyCommand : public SimpleEditCommand { public: - static PassRefPtr<RemoveCSSPropertyCommand> create(Document& document, PassRefPtr<StyledElement> element, CSSPropertyID property) + static Ref<RemoveCSSPropertyCommand> create(Document& document, PassRefPtr<StyledElement> element, CSSPropertyID property) { - return adoptRef(new RemoveCSSPropertyCommand(document, element, property)); + return adoptRef(*new RemoveCSSPropertyCommand(document, element, property)); } private: RemoveCSSPropertyCommand(Document&, PassRefPtr<StyledElement>, CSSPropertyID); ~RemoveCSSPropertyCommand(); - virtual void doApply() override; - virtual void doUnapply() override; + void doApply() override; + void doUnapply() override; #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override; + void getNodesInCommand(HashSet<Node*>&) override; #endif RefPtr<StyledElement> m_element; @@ -58,5 +57,3 @@ private: }; } // namespace WebCore - -#endif // RemoveCSSPropertyCommand_h diff --git a/Source/WebCore/editing/RemoveFormatCommand.cpp b/Source/WebCore/editing/RemoveFormatCommand.cpp index eea0ae8eb..d8b82cf1a 100644 --- a/Source/WebCore/editing/RemoveFormatCommand.cpp +++ b/Source/WebCore/editing/RemoveFormatCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2007 Apple Inc. All rights reserved. * Copyright (C) 2010 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,7 @@ #include "FrameSelection.h" #include "HTMLNames.h" #include "StyleProperties.h" +#include <wtf/NeverDestroyed.h> namespace WebCore { @@ -45,44 +46,44 @@ RemoveFormatCommand::RemoveFormatCommand(Document& document) static bool isElementForRemoveFormatCommand(const Element* element) { - DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, elements, ()); - if (elements.isEmpty()) { - elements.add(acronymTag); - elements.add(bTag); - elements.add(bdoTag); - elements.add(bigTag); - elements.add(citeTag); - elements.add(codeTag); - elements.add(dfnTag); - elements.add(emTag); - elements.add(fontTag); - elements.add(iTag); - elements.add(insTag); - elements.add(kbdTag); - elements.add(nobrTag); - elements.add(qTag); - elements.add(sTag); - elements.add(sampTag); - elements.add(smallTag); - elements.add(strikeTag); - elements.add(strongTag); - elements.add(subTag); - elements.add(supTag); - elements.add(ttTag); - elements.add(uTag); - elements.add(varTag); + static NeverDestroyed<HashSet<QualifiedName>> elements; + if (elements.get().isEmpty()) { + elements.get().add(acronymTag); + elements.get().add(bTag); + elements.get().add(bdoTag); + elements.get().add(bigTag); + elements.get().add(citeTag); + elements.get().add(codeTag); + elements.get().add(dfnTag); + elements.get().add(emTag); + elements.get().add(fontTag); + elements.get().add(iTag); + elements.get().add(insTag); + elements.get().add(kbdTag); + elements.get().add(nobrTag); + elements.get().add(qTag); + elements.get().add(sTag); + elements.get().add(sampTag); + elements.get().add(smallTag); + elements.get().add(strikeTag); + elements.get().add(strongTag); + elements.get().add(subTag); + elements.get().add(supTag); + elements.get().add(ttTag); + elements.get().add(uTag); + elements.get().add(varTag); } - return elements.contains(element->tagQName()); + return elements.get().contains(element->tagQName()); } void RemoveFormatCommand::doApply() { - if (!frame().selection().selection().isNonOrphanedCaretOrRange()) + if (endingSelection().isNoneOrOrphaned()) return; // Get the default style for this editable root, it's the style that we'll give the // content that we're operating on. - Node* root = frame().selection().rootEditableElement(); + Node* root = endingSelection().rootEditableElement(); RefPtr<EditingStyle> defaultStyle = EditingStyle::create(root); // We want to remove everything but transparent background. diff --git a/Source/WebCore/editing/RemoveFormatCommand.h b/Source/WebCore/editing/RemoveFormatCommand.h index 191814554..a493b3abf 100644 --- a/Source/WebCore/editing/RemoveFormatCommand.h +++ b/Source/WebCore/editing/RemoveFormatCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RemoveFormatCommand_h -#define RemoveFormatCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -32,18 +31,16 @@ namespace WebCore { class RemoveFormatCommand : public CompositeEditCommand { public: - static PassRefPtr<RemoveFormatCommand> create(Document& document) + static Ref<RemoveFormatCommand> create(Document& document) { - return adoptRef(new RemoveFormatCommand(document)); + return adoptRef(*new RemoveFormatCommand(document)); } private: explicit RemoveFormatCommand(Document&); - virtual void doApply(); - virtual EditAction editingAction() const { return EditActionUnspecified; } + void doApply() override; + EditAction editingAction() const override { return EditActionUnspecified; } }; } // namespace WebCore - -#endif // RemoveFormatCommand_h diff --git a/Source/WebCore/editing/RemoveNodeCommand.cpp b/Source/WebCore/editing/RemoveNodeCommand.cpp index 67d9c56d6..916a4f8c3 100644 --- a/Source/WebCore/editing/RemoveNodeCommand.cpp +++ b/Source/WebCore/editing/RemoveNodeCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -26,19 +26,17 @@ #include "config.h" #include "RemoveNodeCommand.h" -#include "ExceptionCodePlaceholder.h" -#include "Node.h" #include "RenderElement.h" +#include "htmlediting.h" #include <wtf/Assertions.h> namespace WebCore { -RemoveNodeCommand::RemoveNodeCommand(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) - : SimpleEditCommand(node->document()) - , m_node(node) +RemoveNodeCommand::RemoveNodeCommand(Ref<Node>&& node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable, EditAction editingAction) + : SimpleEditCommand(node->document(), editingAction) + , m_node(WTFMove(node)) , m_shouldAssumeContentIsAlwaysEditable(shouldAssumeContentIsAlwaysEditable) { - ASSERT(m_node); ASSERT(m_node->parentNode()); } @@ -46,24 +44,24 @@ void RemoveNodeCommand::doApply() { ContainerNode* parent = m_node->parentNode(); if (!parent || (m_shouldAssumeContentIsAlwaysEditable == DoNotAssumeContentIsAlwaysEditable - && !parent->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable) && parent->renderer())) + && !isEditableNode(*parent) && parent->renderer())) return; - ASSERT(parent->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable) || !parent->renderer()); + ASSERT(isEditableNode(*parent) || !parent->renderer()); m_parent = parent; m_refChild = m_node->nextSibling(); - m_node->remove(IGNORE_EXCEPTION); + m_node->remove(); } void RemoveNodeCommand::doUnapply() { - RefPtr<ContainerNode> parent = m_parent.release(); - RefPtr<Node> refChild = m_refChild.release(); + RefPtr<ContainerNode> parent = WTFMove(m_parent); + RefPtr<Node> refChild = WTFMove(m_refChild); if (!parent || !parent->hasEditableStyle()) return; - parent->insertBefore(m_node.get(), refChild.get(), IGNORE_EXCEPTION); + parent->insertBefore(m_node, refChild.get()); } #ifndef NDEBUG @@ -71,7 +69,7 @@ void RemoveNodeCommand::getNodesInCommand(HashSet<Node*>& nodes) { addNodeAndDescendants(m_parent.get(), nodes); addNodeAndDescendants(m_refChild.get(), nodes); - addNodeAndDescendants(m_node.get(), nodes); + addNodeAndDescendants(m_node.ptr(), nodes); } #endif diff --git a/Source/WebCore/editing/RemoveNodeCommand.h b/Source/WebCore/editing/RemoveNodeCommand.h index c790c38cc..0c74072d0 100644 --- a/Source/WebCore/editing/RemoveNodeCommand.h +++ b/Source/WebCore/editing/RemoveNodeCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RemoveNodeCommand_h -#define RemoveNodeCommand_h +#pragma once #include "EditCommand.h" @@ -32,27 +31,25 @@ namespace WebCore { class RemoveNodeCommand : public SimpleEditCommand { public: - static PassRefPtr<RemoveNodeCommand> create(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) + static Ref<RemoveNodeCommand> create(Ref<Node>&& node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable, EditAction editingAction = EditActionUnspecified) { - return adoptRef(new RemoveNodeCommand(node, shouldAssumeContentIsAlwaysEditable)); + return adoptRef(*new RemoveNodeCommand(WTFMove(node), shouldAssumeContentIsAlwaysEditable, editingAction)); } private: - explicit RemoveNodeCommand(PassRefPtr<Node>, ShouldAssumeContentIsAlwaysEditable); + RemoveNodeCommand(Ref<Node>&&, ShouldAssumeContentIsAlwaysEditable, EditAction); - virtual void doApply() override; - virtual void doUnapply() override; + void doApply() override; + void doUnapply() override; #ifndef NDEBUG void getNodesInCommand(HashSet<Node*>&) override; #endif - RefPtr<Node> m_node; + Ref<Node> m_node; RefPtr<ContainerNode> m_parent; RefPtr<Node> m_refChild; ShouldAssumeContentIsAlwaysEditable m_shouldAssumeContentIsAlwaysEditable; }; } // namespace WebCore - -#endif // RemoveNodeCommand_h diff --git a/Source/WebCore/editing/RemoveNodePreservingChildrenCommand.cpp b/Source/WebCore/editing/RemoveNodePreservingChildrenCommand.cpp index 573533007..16f0f6310 100644 --- a/Source/WebCore/editing/RemoveNodePreservingChildrenCommand.cpp +++ b/Source/WebCore/editing/RemoveNodePreservingChildrenCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -31,8 +31,8 @@ namespace WebCore { -RemoveNodePreservingChildrenCommand::RemoveNodePreservingChildrenCommand(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) - : CompositeEditCommand(node->document()) +RemoveNodePreservingChildrenCommand::RemoveNodePreservingChildrenCommand(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable, EditAction editingAction) + : CompositeEditCommand(node->document(), editingAction) , m_node(node) , m_shouldAssumeContentIsAlwaysEditable(shouldAssumeContentIsAlwaysEditable) { @@ -47,9 +47,9 @@ void RemoveNodePreservingChildrenCommand::doApply() size_t size = children.size(); for (size_t i = 0; i < size; ++i) { - RefPtr<Node> child = children[i].release(); + auto child = WTFMove(children[i]); removeNode(child, m_shouldAssumeContentIsAlwaysEditable); - insertNodeBefore(child.release(), m_node, m_shouldAssumeContentIsAlwaysEditable); + insertNodeBefore(WTFMove(child), m_node, m_shouldAssumeContentIsAlwaysEditable); } removeNode(m_node, m_shouldAssumeContentIsAlwaysEditable); } diff --git a/Source/WebCore/editing/RemoveNodePreservingChildrenCommand.h b/Source/WebCore/editing/RemoveNodePreservingChildrenCommand.h index 86a3b18e1..07ea088ba 100644 --- a/Source/WebCore/editing/RemoveNodePreservingChildrenCommand.h +++ b/Source/WebCore/editing/RemoveNodePreservingChildrenCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RemoveNodePreservingChildrenCommand_h -#define RemoveNodePreservingChildrenCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -32,20 +31,18 @@ namespace WebCore { class RemoveNodePreservingChildrenCommand : public CompositeEditCommand { public: - static PassRefPtr<RemoveNodePreservingChildrenCommand> create(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) + static Ref<RemoveNodePreservingChildrenCommand> create(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable, EditAction editingAction) { - return adoptRef(new RemoveNodePreservingChildrenCommand(node, shouldAssumeContentIsAlwaysEditable)); + return adoptRef(*new RemoveNodePreservingChildrenCommand(node, shouldAssumeContentIsAlwaysEditable, editingAction)); } private: - explicit RemoveNodePreservingChildrenCommand(PassRefPtr<Node>, ShouldAssumeContentIsAlwaysEditable); + explicit RemoveNodePreservingChildrenCommand(PassRefPtr<Node>, ShouldAssumeContentIsAlwaysEditable, EditAction); - virtual void doApply(); + void doApply() override; RefPtr<Node> m_node; ShouldAssumeContentIsAlwaysEditable m_shouldAssumeContentIsAlwaysEditable; }; } // namespace WebCore - -#endif // RemoveNodePreservingChildrenCommand_h diff --git a/Source/WebCore/editing/RenderedPosition.cpp b/Source/WebCore/editing/RenderedPosition.cpp index 4d628f9db..9938218b4 100644 --- a/Source/WebCore/editing/RenderedPosition.cpp +++ b/Source/WebCore/editing/RenderedPosition.cpp @@ -39,7 +39,7 @@ namespace WebCore { static inline RenderObject* rendererFromPosition(const Position& position) { ASSERT(position.isNotNull()); - Node* rendererNode = 0; + Node* rendererNode = nullptr; switch (position.anchorType()) { case Position::PositionIsOffsetInAnchor: rendererNode = position.computeNodeAfterPosition(); @@ -64,9 +64,7 @@ static inline RenderObject* rendererFromPosition(const Position& position) } RenderedPosition::RenderedPosition(const VisiblePosition& position) - : m_renderer(0) - , m_inlineBox(0) - , m_offset(0) + : m_offset(0) , m_prevLeafChild(uncachedInlineBox()) , m_nextLeafChild(uncachedInlineBox()) { @@ -80,9 +78,7 @@ RenderedPosition::RenderedPosition(const VisiblePosition& position) } RenderedPosition::RenderedPosition(const Position& position, EAffinity affinity) - : m_renderer(0) - , m_inlineBox(0) - , m_offset(0) + : m_offset(0) , m_prevLeafChild(uncachedInlineBox()) , m_nextLeafChild(uncachedInlineBox()) { @@ -227,7 +223,7 @@ IntRect RenderedPosition::absoluteRect(LayoutUnit* extraWidthToEndOfLine) const if (isNull()) return IntRect(); - IntRect localRect = pixelSnappedIntRect(m_renderer->localCaretRect(m_inlineBox, m_offset, extraWidthToEndOfLine)); + IntRect localRect = snappedIntRect(m_renderer->localCaretRect(m_inlineBox, m_offset, extraWidthToEndOfLine)); return localRect == IntRect() ? IntRect() : m_renderer->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox(); } diff --git a/Source/WebCore/editing/RenderedPosition.h b/Source/WebCore/editing/RenderedPosition.h index 4f7eb2832..729c1abdb 100644 --- a/Source/WebCore/editing/RenderedPosition.h +++ b/Source/WebCore/editing/RenderedPosition.h @@ -28,8 +28,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderedPosition_h -#define RenderedPosition_h +#pragma once #include "InlineBox.h" #include "TextAffinity.h" @@ -67,7 +66,7 @@ public: Position positionAtLeftBoundaryOfBiDiRun() const; Position positionAtRightBoundaryOfBiDiRun() const; - IntRect absoluteRect(LayoutUnit* extraWidthToEndOfLine = 0) const; + IntRect absoluteRect(LayoutUnit* extraWidthToEndOfLine = nullptr) const; private: bool operator==(const RenderedPosition&) const { return false; } @@ -80,8 +79,8 @@ private: bool atLeftBoundaryOfBidiRun(ShouldMatchBidiLevel, unsigned char bidiLevelOfRun) const; bool atRightBoundaryOfBidiRun(ShouldMatchBidiLevel, unsigned char bidiLevelOfRun) const; - RenderObject* m_renderer; - InlineBox* m_inlineBox; + RenderObject* m_renderer { nullptr }; + InlineBox* m_inlineBox { nullptr }; int m_offset; static InlineBox* uncachedInlineBox() { return reinterpret_cast<InlineBox*>(1); } @@ -92,9 +91,7 @@ private: }; inline RenderedPosition::RenderedPosition() - : m_renderer(0) - , m_inlineBox(0) - , m_offset(0) + : m_offset(0) , m_prevLeafChild(uncachedInlineBox()) , m_nextLeafChild(uncachedInlineBox()) { @@ -111,6 +108,4 @@ inline RenderedPosition::RenderedPosition(RenderObject* renderer, InlineBox* box bool renderObjectContainsPosition(RenderObject*, const Position&); -}; - -#endif // RenderedPosition_h +} // namespace WebCore diff --git a/Source/WebCore/editing/ReplaceNodeWithSpanCommand.cpp b/Source/WebCore/editing/ReplaceNodeWithSpanCommand.cpp index d417ed538..e1d0223e3 100644 --- a/Source/WebCore/editing/ReplaceNodeWithSpanCommand.cpp +++ b/Source/WebCore/editing/ReplaceNodeWithSpanCommand.cpp @@ -31,16 +31,11 @@ #include "config.h" #include "ReplaceNodeWithSpanCommand.h" +#include "HTMLSpanElement.h" #include "htmlediting.h" -#include "HTMLElement.h" -#include "HTMLNames.h" - -#include <wtf/Assertions.h> namespace WebCore { -using namespace HTMLNames; - ReplaceNodeWithSpanCommand::ReplaceNodeWithSpanCommand(PassRefPtr<HTMLElement> element) : SimpleEditCommand(element->document()) , m_elementToReplace(element) @@ -48,36 +43,36 @@ ReplaceNodeWithSpanCommand::ReplaceNodeWithSpanCommand(PassRefPtr<HTMLElement> e ASSERT(m_elementToReplace); } -static void swapInNodePreservingAttributesAndChildren(HTMLElement* newNode, HTMLElement* nodeToReplace) +static void swapInNodePreservingAttributesAndChildren(HTMLElement& newNode, HTMLElement& nodeToReplace) { - ASSERT(nodeToReplace->inDocument()); - RefPtr<ContainerNode> parentNode = nodeToReplace->parentNode(); + ASSERT(nodeToReplace.isConnected()); + RefPtr<ContainerNode> parentNode = nodeToReplace.parentNode(); // FIXME: Fix this to send the proper MutationRecords when MutationObservers are present. - newNode->cloneDataFromElement(*nodeToReplace); + newNode.cloneDataFromElement(nodeToReplace); NodeVector children; - getChildNodes(*nodeToReplace, children); - for (size_t i = 0; i < children.size(); ++i) - newNode->appendChild(&children[i].get(), ASSERT_NO_EXCEPTION); + getChildNodes(nodeToReplace, children); + for (auto& child : children) + newNode.appendChild(child); - parentNode->insertBefore(newNode, nodeToReplace, ASSERT_NO_EXCEPTION); - parentNode->removeChild(nodeToReplace, ASSERT_NO_EXCEPTION); + parentNode->insertBefore(newNode, &nodeToReplace); + parentNode->removeChild(nodeToReplace); } void ReplaceNodeWithSpanCommand::doApply() { - if (!m_elementToReplace->inDocument()) + if (!m_elementToReplace->isConnected()) return; if (!m_spanElement) - m_spanElement = createHTMLElement(m_elementToReplace->document(), spanTag); - swapInNodePreservingAttributesAndChildren(m_spanElement.get(), m_elementToReplace.get()); + m_spanElement = HTMLSpanElement::create(m_elementToReplace->document()); + swapInNodePreservingAttributesAndChildren(*m_spanElement, *m_elementToReplace); } void ReplaceNodeWithSpanCommand::doUnapply() { - if (!m_spanElement->inDocument()) + if (!m_spanElement->isConnected()) return; - swapInNodePreservingAttributesAndChildren(m_elementToReplace.get(), m_spanElement.get()); + swapInNodePreservingAttributesAndChildren(*m_elementToReplace, *m_spanElement); } #ifndef NDEBUG diff --git a/Source/WebCore/editing/ReplaceNodeWithSpanCommand.h b/Source/WebCore/editing/ReplaceNodeWithSpanCommand.h index faa2da45a..b564c66ec 100644 --- a/Source/WebCore/editing/ReplaceNodeWithSpanCommand.h +++ b/Source/WebCore/editing/ReplaceNodeWithSpanCommand.h @@ -28,8 +28,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ReplaceNodeWithSpanCommand_h -#define ReplaceNodeWithSpanCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -40,9 +39,9 @@ class HTMLElement; // More accurately, this is ReplaceElementWithSpanPreservingChildrenAndAttributesCommand class ReplaceNodeWithSpanCommand : public SimpleEditCommand { public: - static PassRefPtr<ReplaceNodeWithSpanCommand> create(PassRefPtr<HTMLElement> element) + static Ref<ReplaceNodeWithSpanCommand> create(PassRefPtr<HTMLElement> element) { - return adoptRef(new ReplaceNodeWithSpanCommand(element)); + return adoptRef(*new ReplaceNodeWithSpanCommand(element)); } HTMLElement* spanElement() { return m_spanElement.get(); } @@ -50,11 +49,11 @@ public: private: explicit ReplaceNodeWithSpanCommand(PassRefPtr<HTMLElement>); - virtual void doApply() override; - virtual void doUnapply() override; + void doApply() override; + void doUnapply() override; #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override; + void getNodesInCommand(HashSet<Node*>&) override; #endif RefPtr<HTMLElement> m_elementToReplace; @@ -62,5 +61,3 @@ private: }; } // namespace WebCore - -#endif // ReplaceNodeWithSpanCommand diff --git a/Source/WebCore/editing/ReplaceRangeWithTextCommand.cpp b/Source/WebCore/editing/ReplaceRangeWithTextCommand.cpp new file mode 100644 index 000000000..5b34d0643 --- /dev/null +++ b/Source/WebCore/editing/ReplaceRangeWithTextCommand.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 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 "ReplaceRangeWithTextCommand.h" + +#include "AlternativeTextController.h" +#include "DataTransfer.h" +#include "Document.h" +#include "DocumentFragment.h" +#include "Editor.h" +#include "Frame.h" +#include "ReplaceSelectionCommand.h" +#include "SetSelectionCommand.h" +#include "StaticRange.h" +#include "TextIterator.h" +#include "markup.h" + +namespace WebCore { + +ReplaceRangeWithTextCommand::ReplaceRangeWithTextCommand(RefPtr<Range> rangeToBeReplaced, const String& text) + : CompositeEditCommand(rangeToBeReplaced->startContainer().document(), EditActionInsertReplacement) + , m_rangeToBeReplaced(rangeToBeReplaced) + , m_text(text) +{ +} + +bool ReplaceRangeWithTextCommand::willApplyCommand() +{ + m_textFragment = createFragmentFromText(*m_rangeToBeReplaced, m_text); + return CompositeEditCommand::willApplyCommand(); +} + +void ReplaceRangeWithTextCommand::doApply() +{ + VisibleSelection selection = *m_rangeToBeReplaced; + + if (!m_rangeToBeReplaced) + return; + + if (!frame().selection().shouldChangeSelection(selection)) + return; + + String previousText = plainText(m_rangeToBeReplaced.get()); + if (!previousText.length()) + return; + + applyCommandToComposite(SetSelectionCommand::create(selection, FrameSelection::defaultSetSelectionOptions())); + applyCommandToComposite(ReplaceSelectionCommand::create(document(), WTFMove(m_textFragment), ReplaceSelectionCommand::MatchStyle, EditActionPaste)); +} + +String ReplaceRangeWithTextCommand::inputEventData() const +{ + if (isEditingTextAreaOrTextInput()) + return m_text; + + return CompositeEditCommand::inputEventData(); +} + +RefPtr<DataTransfer> ReplaceRangeWithTextCommand::inputEventDataTransfer() const +{ + if (!isEditingTextAreaOrTextInput()) + return DataTransfer::createForInputEvent(m_text, createMarkup(*m_textFragment)); + + return CompositeEditCommand::inputEventDataTransfer(); +} + +Vector<RefPtr<StaticRange>> ReplaceRangeWithTextCommand::targetRanges() const +{ + RefPtr<StaticRange> range = StaticRange::createFromRange(*m_rangeToBeReplaced); + return { 1, range }; +} + +} // namespace WebCore diff --git a/Source/WebCore/editing/DeleteButton.h b/Source/WebCore/editing/ReplaceRangeWithTextCommand.h index b98ba06a7..098307f65 100644 --- a/Source/WebCore/editing/DeleteButton.h +++ b/Source/WebCore/editing/ReplaceRangeWithTextCommand.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,40 +10,46 @@ * 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 + * 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 COMPUTER, INC. OR + * 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. + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef DeleteButton_h -#define DeleteButton_h +#pragma once -#include "HTMLImageElement.h" +#include "CompositeEditCommand.h" +#include "Range.h" namespace WebCore { -class DeleteButton : public HTMLImageElement { -public: - static PassRefPtr<DeleteButton> create(Document&); +class DocumentFragment; -#if !PLATFORM(IOS) - virtual bool willRespondToMouseClickEvents() override { return true; } -#endif // !PLATFORM(IOS) +class ReplaceRangeWithTextCommand : public CompositeEditCommand { +public: + static Ref<ReplaceRangeWithTextCommand> create(RefPtr<Range> rangeToBeReplaced, const String& text) + { + return adoptRef(*new ReplaceRangeWithTextCommand(rangeToBeReplaced, text)); + } private: - explicit DeleteButton(Document&); - - virtual void defaultEventHandler(Event*); + ReplaceRangeWithTextCommand(RefPtr<Range> rangeToBeReplaced, const String& text); + bool willApplyCommand() final; + void doApply() override; + String inputEventData() const final; + RefPtr<DataTransfer> inputEventDataTransfer() const final; + Vector<RefPtr<StaticRange>> targetRanges() const final; + + RefPtr<Range> m_rangeToBeReplaced; + RefPtr<DocumentFragment> m_textFragment; + String m_text; }; -} // namespace - -#endif +} // namespace WebCore diff --git a/Source/WebCore/editing/ReplaceSelectionCommand.cpp b/Source/WebCore/editing/ReplaceSelectionCommand.cpp index 648692ad0..d4ddea69d 100644 --- a/Source/WebCore/editing/ReplaceSelectionCommand.cpp +++ b/Source/WebCore/editing/ReplaceSelectionCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005, 2006, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2005-2017 Apple Inc. All rights reserved. * Copyright (C) 2009, 2010, 2011 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -11,10 +11,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -27,25 +27,31 @@ #include "config.h" #include "ReplaceSelectionCommand.h" +#include "AXObjectCache.h" #include "ApplyStyleCommand.h" #include "BeforeTextInsertedEvent.h" #include "BreakBlockquoteCommand.h" #include "CSSStyleDeclaration.h" +#include "DOMWrapperWorld.h" +#include "DataTransfer.h" #include "Document.h" #include "DocumentFragment.h" -#include "Element.h" #include "ElementIterator.h" #include "EventNames.h" -#include "ExceptionCodePlaceholder.h" #include "Frame.h" #include "FrameSelection.h" +#include "HTMLBRElement.h" +#include "HTMLBaseElement.h" #include "HTMLInputElement.h" +#include "HTMLLIElement.h" +#include "HTMLLinkElement.h" +#include "HTMLMetaElement.h" #include "HTMLNames.h" +#include "HTMLStyleElement.h" #include "HTMLTitleElement.h" #include "NodeList.h" #include "NodeRenderStyle.h" #include "RenderInline.h" -#include "RenderObject.h" #include "RenderText.h" #include "SimplifyMarkupCommand.h" #include "SmartReplace.h" @@ -55,6 +61,7 @@ #include "VisibleUnits.h" #include "htmlediting.h" #include "markup.h" +#include <wtf/NeverDestroyed.h> #include <wtf/StdLibExtras.h> namespace WebCore { @@ -63,6 +70,8 @@ using namespace HTMLNames; enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment }; +static void removeHeadContents(ReplacementFragment&); + // --- ReplacementFragment helper class class ReplacementFragment { @@ -84,7 +93,7 @@ public: void removeNodePreservingChildren(PassRefPtr<Node>); private: - PassRefPtr<StyledElement> insertFragmentForTestRendering(Node* rootEditableNode); + Ref<HTMLElement> insertFragmentForTestRendering(Node* rootEditableNode); void removeUnrenderedNodes(Node*); void restoreAndRemoveTestRenderingNodesToFragment(StyledElement*); void removeInterchangeNodes(Node*); @@ -99,44 +108,46 @@ private: bool m_hasInterchangeNewlineAtEnd; }; -static bool isInterchangeNewlineNode(const Node *node) +static bool isInterchangeNewlineNode(const Node* node) { - DEFINE_STATIC_LOCAL(String, interchangeNewlineClassString, (AppleInterchangeNewline)); - return node && node->hasTagName(brTag) && - static_cast<const Element *>(node)->getAttribute(classAttr) == interchangeNewlineClassString; + static NeverDestroyed<String> interchangeNewlineClassString(AppleInterchangeNewline); + return is<HTMLBRElement>(node) && downcast<HTMLBRElement>(*node).attributeWithoutSynchronization(classAttr) == interchangeNewlineClassString; } -static bool isInterchangeConvertedSpaceSpan(const Node *node) +static bool isInterchangeConvertedSpaceSpan(const Node* node) { - DEFINE_STATIC_LOCAL(String, convertedSpaceSpanClassString, (AppleConvertedSpace)); - return node->isHTMLElement() && - static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString; + static NeverDestroyed<String> convertedSpaceSpanClassString(AppleConvertedSpace); + return is<HTMLElement>(node) && downcast<HTMLElement>(*node).attributeWithoutSynchronization(classAttr) == convertedSpaceSpanClassString; } -static Position positionAvoidingPrecedingNodes(Position pos) +static Position positionAvoidingPrecedingNodes(Position position) { + ASSERT(position.isNotNull()); + // If we're already on a break, it's probably a placeholder and we shouldn't change our position. - if (editingIgnoresContent(pos.deprecatedNode())) - return pos; + if (editingIgnoresContent(*position.deprecatedNode())) + return position; // We also stop when changing block flow elements because even though the visual position is the // 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* enclosingBlockNode = enclosingBlock(pos.containerNode()); - for (Position nextPosition = pos; nextPosition.containerNode() != enclosingBlockNode; pos = nextPosition) { - if (lineBreakExistsAtPosition(pos)) + auto* enclosingBlockNode = enclosingBlock(position.containerNode()); + for (Position nextPosition = position; nextPosition.containerNode() != enclosingBlockNode; position = nextPosition) { + if (lineBreakExistsAtPosition(position)) break; - if (pos.containerNode()->nonShadowBoundaryParentNode()) - nextPosition = positionInParentAfterNode(pos.containerNode()); - - if (nextPosition == pos - || enclosingBlock(nextPosition.containerNode()) != enclosingBlockNode - || VisiblePosition(pos) != VisiblePosition(nextPosition)) + if (position.containerNode()->nonShadowBoundaryParentNode()) + nextPosition = positionInParentAfterNode(position.containerNode()); + + if (nextPosition == position) + break; + if (enclosingBlock(nextPosition.containerNode()) != enclosingBlockNode) + break; + if (VisiblePosition(position) != VisiblePosition(nextPosition)) break; } - return pos; + return position; } ReplacementFragment::ReplacementFragment(Document& document, DocumentFragment* fragment, const VisibleSelection& selection) @@ -157,10 +168,9 @@ ReplacementFragment::ReplacementFragment(Document& document, DocumentFragment* f Node* shadowAncestorNode = editableRoot->deprecatedShadowAncestorNode(); - if (!editableRoot->getAttributeEventListener(eventNames().webkitBeforeTextInsertedEvent) && - // FIXME: Remove these checks once textareas and textfields actually register an event handler. - !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextControl()) && - editableRoot->hasRichlyEditableStyle()) { + if (!editableRoot->attributeEventListener(eventNames().webkitBeforeTextInsertedEvent, mainThreadNormalWorld()) + && !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextControl()) + && editableRoot->hasRichlyEditableStyle()) { removeInterchangeNodes(m_fragment.get()); return; } @@ -179,16 +189,16 @@ ReplacementFragment::ReplacementFragment(Document& document, DocumentFragment* f restoreAndRemoveTestRenderingNodesToFragment(holder.get()); // Give the root a chance to change the text. - RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text); - editableRoot->dispatchEvent(evt, ASSERT_NO_EXCEPTION); - if (text != evt->text() || !editableRoot->hasRichlyEditableStyle()) { + auto event = BeforeTextInsertedEvent::create(text); + editableRoot->dispatchEvent(event); + if (text != event->text() || !editableRoot->hasRichlyEditableStyle()) { restoreAndRemoveTestRenderingNodesToFragment(holder.get()); RefPtr<Range> range = selection.toNormalizedRange(); if (!range) return; - m_fragment = createFragmentFromText(*range, evt->text()); + m_fragment = createFragmentFromText(*range, event->text()); if (!m_fragment->firstChild()) return; @@ -221,9 +231,9 @@ void ReplacementFragment::removeNodePreservingChildren(PassRefPtr<Node> node) while (RefPtr<Node> n = node->firstChild()) { removeNode(n); - insertNodeBefore(n.release(), node.get()); + insertNodeBefore(WTFMove(n), node.get()); } - removeNode(node); + removeNode(WTFMove(node)); } void ReplacementFragment::removeNode(PassRefPtr<Node> node) @@ -235,7 +245,7 @@ void ReplacementFragment::removeNode(PassRefPtr<Node> node) if (!parent) return; - parent->removeChild(node.get(), ASSERT_NO_EXCEPTION); + parent->removeChild(*node); } void ReplacementFragment::insertNodeBefore(PassRefPtr<Node> node, Node* refNode) @@ -247,18 +257,18 @@ void ReplacementFragment::insertNodeBefore(PassRefPtr<Node> node, Node* refNode) if (!parent) return; - parent->insertBefore(node, refNode, ASSERT_NO_EXCEPTION); + parent->insertBefore(*node, refNode); } -PassRefPtr<StyledElement> ReplacementFragment::insertFragmentForTestRendering(Node* rootEditableElement) +Ref<HTMLElement> ReplacementFragment::insertFragmentForTestRendering(Node* rootEditableElement) { - RefPtr<StyledElement> holder = createDefaultParagraphElement(document()); + auto holder = createDefaultParagraphElement(document()); - holder->appendChild(m_fragment, ASSERT_NO_EXCEPTION); - rootEditableElement->appendChild(holder.get(), ASSERT_NO_EXCEPTION); + holder->appendChild(*m_fragment); + rootEditableElement->appendChild(holder); document().updateLayoutIgnorePendingStylesheets(); - return holder.release(); + return holder; } void ReplacementFragment::restoreAndRemoveTestRenderingNodesToFragment(StyledElement* holder) @@ -267,8 +277,8 @@ void ReplacementFragment::restoreAndRemoveTestRenderingNodesToFragment(StyledEle return; while (RefPtr<Node> node = holder->firstChild()) { - holder->removeChild(node.get(), ASSERT_NO_EXCEPTION); - m_fragment->appendChild(node.get(), ASSERT_NO_EXCEPTION); + holder->removeChild(*node); + m_fragment->appendChild(*node); } removeNode(holder); @@ -278,13 +288,13 @@ void ReplacementFragment::removeUnrenderedNodes(Node* holder) { Vector<RefPtr<Node>> unrendered; - for (Node* node = holder->firstChild(); node; node = NodeTraversal::next(node, holder)) - if (!isNodeRendered(node) && !isTableStructureNode(node)) + for (Node* node = holder->firstChild(); node; node = NodeTraversal::next(*node, holder)) { + if (!isNodeRendered(*node) && !isTableStructureNode(node)) unrendered.append(node); + } - size_t n = unrendered.size(); - for (size_t i = 0; i < n; ++i) - removeNode(unrendered[i]); + for (auto& node : unrendered) + removeNode(node); } void ReplacementFragment::removeInterchangeNodes(Node* container) @@ -319,9 +329,9 @@ void ReplacementFragment::removeInterchangeNodes(Node* container) node = container->firstChild(); while (node) { - RefPtr<Node> next = NodeTraversal::next(node); + RefPtr<Node> next = NodeTraversal::next(*node); if (isInterchangeConvertedSpaceSpan(node)) { - next = NodeTraversal::nextSkippingChildren(node); + next = NodeTraversal::nextSkippingChildren(*node); removeNodePreservingChildren(node); } node = next.get(); @@ -342,20 +352,20 @@ inline void ReplaceSelectionCommand::InsertedNodes::respondToNodeInsertion(Node* inline void ReplaceSelectionCommand::InsertedNodes::willRemoveNodePreservingChildren(Node* node) { if (m_firstNodeInserted == node) - m_firstNodeInserted = NodeTraversal::next(node); + m_firstNodeInserted = NodeTraversal::next(*node); if (m_lastNodeInserted == node) - m_lastNodeInserted = node->lastChild() ? node->lastChild() : NodeTraversal::nextSkippingChildren(node); + m_lastNodeInserted = node->lastChild() ? node->lastChild() : NodeTraversal::nextSkippingChildren(*node); } inline void ReplaceSelectionCommand::InsertedNodes::willRemoveNode(Node* node) { if (m_firstNodeInserted == node && m_lastNodeInserted == node) { - m_firstNodeInserted = 0; - m_lastNodeInserted = 0; + m_firstNodeInserted = nullptr; + m_lastNodeInserted = nullptr; } else if (m_firstNodeInserted == node) - m_firstNodeInserted = NodeTraversal::nextSkippingChildren(m_firstNodeInserted.get()); + m_firstNodeInserted = NodeTraversal::nextSkippingChildren(*m_firstNodeInserted); else if (m_lastNodeInserted == node) - m_lastNodeInserted = NodeTraversal::previousSkippingChildren(m_lastNodeInserted.get()); + m_lastNodeInserted = NodeTraversal::previousSkippingChildren(*m_lastNodeInserted); } inline void ReplaceSelectionCommand::InsertedNodes::didReplaceNode(Node* node, Node* newNode) @@ -366,17 +376,17 @@ inline void ReplaceSelectionCommand::InsertedNodes::didReplaceNode(Node* node, N m_lastNodeInserted = newNode; } -ReplaceSelectionCommand::ReplaceSelectionCommand(Document& document, PassRefPtr<DocumentFragment> fragment, CommandOptions options, EditAction editAction) - : CompositeEditCommand(document) +ReplaceSelectionCommand::ReplaceSelectionCommand(Document& document, RefPtr<DocumentFragment>&& fragment, CommandOptions options, EditAction editAction) + : CompositeEditCommand(document, editAction) , m_selectReplacement(options & SelectReplacement) , m_smartReplace(options & SmartReplace) , m_matchStyle(options & MatchStyle) , m_documentFragment(fragment) , m_preventNesting(options & PreventNesting) , m_movingParagraph(options & MovingParagraph) - , m_editAction(editAction) , m_sanitizeFragment(options & SanitizeFragment) , m_shouldMergeEnd(false) + , m_ignoreMailBlockquote(options & IgnoreMailBlockquote) { } @@ -428,7 +438,7 @@ bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph) static bool isMailPasteAsQuotationNode(const Node* node) { - return node && node->hasTagName(blockquoteTag) && node->isElementNode() && toElement(node)->getAttribute(classAttr) == ApplePasteAsQuotation; + return node && node->hasTagName(blockquoteTag) && downcast<Element>(node)->attributeWithoutSynchronization(classAttr) == ApplePasteAsQuotation; } static bool isHeaderElement(const Node* a) @@ -446,7 +456,7 @@ static bool isHeaderElement(const Node* a) static bool haveSameTagName(Node* a, Node* b) { - return a && b && a->isElementNode() && b->isElementNode() && toElement(a)->tagName() == toElement(b)->tagName(); + return is<Element>(a) && is<Element>(b) && downcast<Element>(*a).tagName() == downcast<Element>(*b).tagName(); } bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& source, const VisiblePosition& destination) @@ -454,18 +464,19 @@ bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& source, const V if (source.isNull() || destination.isNull()) return false; - Node* sourceNode = source.deepEquivalent().deprecatedNode(); - Node* destinationNode = destination.deepEquivalent().deprecatedNode(); - Node* sourceBlock = enclosingBlock(sourceNode); - Node* destinationBlock = enclosingBlock(destinationNode); - return !enclosingNodeOfType(source.deepEquivalent(), &isMailPasteAsQuotationNode) && - sourceBlock && (!sourceBlock->hasTagName(blockquoteTag) || isMailBlockquote(sourceBlock)) && - enclosingListChild(sourceBlock) == enclosingListChild(destinationNode) && - enclosingTableCell(source.deepEquivalent()) == enclosingTableCell(destination.deepEquivalent()) && - (!isHeaderElement(sourceBlock) || haveSameTagName(sourceBlock, destinationBlock)) && - // Don't merge to or from a position before or after a block because it would - // be a no-op and cause infinite recursion. - !isBlock(sourceNode) && !isBlock(destinationNode); + auto* sourceNode = source.deepEquivalent().deprecatedNode(); + auto* destinationNode = destination.deepEquivalent().deprecatedNode(); + auto* sourceBlock = enclosingBlock(sourceNode); + auto* destinationBlock = enclosingBlock(destinationNode); + return !enclosingNodeOfType(source.deepEquivalent(), &isMailPasteAsQuotationNode) + && sourceBlock + && (!sourceBlock->hasTagName(blockquoteTag) || isMailBlockquote(sourceBlock)) + && enclosingListChild(sourceBlock) == enclosingListChild(destinationNode) + && enclosingTableCell(source.deepEquivalent()) == enclosingTableCell(destination.deepEquivalent()) + && (!isHeaderElement(sourceBlock) || haveSameTagName(sourceBlock, destinationBlock)) + // Don't merge to or from a position before or after a block because it would + // be a no-op and cause infinite recursion. + && !isBlock(sourceNode) && !isBlock(destinationNode); } // Style rules that match just inserted elements could change their appearance, like @@ -477,29 +488,29 @@ void ReplaceSelectionCommand::removeRedundantStylesAndKeepStyleSpanInline(Insert for (RefPtr<Node> node = insertedNodes.firstNodeInserted(); node && node != pastEndNode; node = next) { // FIXME: <rdar://problem/5371536> Style rules that match pasted content can change it's appearance - next = NodeTraversal::next(node.get()); - if (!node->isStyledElement()) + next = NodeTraversal::next(*node); + if (!is<StyledElement>(*node)) continue; - StyledElement* element = toStyledElement(node.get()); + StyledElement* element = downcast<StyledElement>(node.get()); const StyleProperties* inlineStyle = element->inlineStyle(); RefPtr<EditingStyle> newInlineStyle = EditingStyle::create(inlineStyle); if (inlineStyle) { - if (element->isHTMLElement()) { + if (is<HTMLElement>(*element)) { Vector<QualifiedName> attributes; - HTMLElement* htmlElement = toHTMLElement(element); + HTMLElement& htmlElement = downcast<HTMLElement>(*element); - if (newInlineStyle->conflictsWithImplicitStyleOfElement(htmlElement)) { + if (newInlineStyle->conflictsWithImplicitStyleOfElement(&htmlElement)) { // e.g. <b style="font-weight: normal;"> is converted to <span style="font-weight: normal;"> - node = replaceElementWithSpanPreservingChildrenAndAttributes(htmlElement); - element = toStyledElement(node.get()); - insertedNodes.didReplaceNode(htmlElement, node.get()); - } else if (newInlineStyle->extractConflictingImplicitStyleOfAttributes(htmlElement, EditingStyle::PreserveWritingDirection, 0, attributes, + node = replaceElementWithSpanPreservingChildrenAndAttributes(&htmlElement); + element = downcast<StyledElement>(node.get()); + insertedNodes.didReplaceNode(&htmlElement, node.get()); + } else if (newInlineStyle->extractConflictingImplicitStyleOfAttributes(&htmlElement, EditingStyle::PreserveWritingDirection, 0, attributes, EditingStyle::DoNotExtractMatchingStyle)) { // e.g. <font size="3" style="font-size: 20px;"> is converted to <font style="font-size: 20px;"> - for (size_t i = 0; i < attributes.size(); i++) - removeNodeAttribute(element, attributes[i]); + for (auto& attribute : attributes) + removeNodeAttribute(element, attribute); } } @@ -525,7 +536,7 @@ void ReplaceSelectionCommand::removeRedundantStylesAndKeepStyleSpanInline(Insert setNodeAttribute(element, styleAttr, newInlineStyle->style()->asText()); // FIXME: Tolerate differences in id, class, and style attributes. - if (isNonTableCellHTMLBlockElement(element) && areIdenticalElements(element, element->parentNode()) + if (element->parentNode() && isNonTableCellHTMLBlockElement(element) && areIdenticalElements(*element, *element->parentNode()) && VisiblePosition(firstPositionInNode(element->parentNode())) == VisiblePosition(firstPositionInNode(element)) && VisiblePosition(lastPositionInNode(element->parentNode())) == VisiblePosition(lastPositionInNode(element))) { insertedNodes.willRemoveNodePreservingChildren(element); @@ -533,7 +544,7 @@ void ReplaceSelectionCommand::removeRedundantStylesAndKeepStyleSpanInline(Insert continue; } - if (element->parentNode()->hasRichlyEditableStyle()) + if (element->parentNode() && element->parentNode()->hasRichlyEditableStyle()) removeNodeAttribute(element, contenteditableAttr); // WebKit used to not add display: inline and float: none on copy. @@ -553,9 +564,9 @@ void ReplaceSelectionCommand::removeRedundantStylesAndKeepStyleSpanInline(Insert // Mutate using the CSSOM wrapper so we get the same event behavior as a script. if (isBlock(element)) - element->style()->setPropertyInternal(CSSPropertyDisplay, "inline", false, IGNORE_EXCEPTION); + element->cssomStyle()->setPropertyInternal(CSSPropertyDisplay, "inline", false); if (element->renderer() && element->renderer()->style().isFloating()) - element->style()->setPropertyInternal(CSSPropertyFloat, "none", false, IGNORE_EXCEPTION); + element->cssomStyle()->setPropertyInternal(CSSPropertyFloat, "none", false); } } } @@ -563,58 +574,58 @@ void ReplaceSelectionCommand::removeRedundantStylesAndKeepStyleSpanInline(Insert static bool isProhibitedParagraphChild(const AtomicString& name) { // https://dvcs.w3.org/hg/editing/raw-file/57abe6d3cb60/editing.html#prohibited-paragraph-child - DEFINE_STATIC_LOCAL(HashSet<AtomicString>, elements, ()); - if (elements.isEmpty()) { - elements.add(addressTag.localName()); - elements.add(articleTag.localName()); - elements.add(asideTag.localName()); - elements.add(blockquoteTag.localName()); - elements.add(captionTag.localName()); - elements.add(centerTag.localName()); - elements.add(colTag.localName()); - elements.add(colgroupTag.localName()); - elements.add(ddTag.localName()); - elements.add(detailsTag.localName()); - elements.add(dirTag.localName()); - elements.add(divTag.localName()); - elements.add(dlTag.localName()); - elements.add(dtTag.localName()); - elements.add(fieldsetTag.localName()); - elements.add(figcaptionTag.localName()); - elements.add(figureTag.localName()); - elements.add(footerTag.localName()); - elements.add(formTag.localName()); - elements.add(h1Tag.localName()); - elements.add(h2Tag.localName()); - elements.add(h3Tag.localName()); - elements.add(h4Tag.localName()); - elements.add(h5Tag.localName()); - elements.add(h6Tag.localName()); - elements.add(headerTag.localName()); - elements.add(hgroupTag.localName()); - elements.add(hrTag.localName()); - elements.add(liTag.localName()); - elements.add(listingTag.localName()); - elements.add(mainTag.localName()); // Missing in the specification. - elements.add(menuTag.localName()); - elements.add(navTag.localName()); - elements.add(olTag.localName()); - elements.add(pTag.localName()); - elements.add(plaintextTag.localName()); - elements.add(preTag.localName()); - elements.add(sectionTag.localName()); - elements.add(summaryTag.localName()); - elements.add(tableTag.localName()); - elements.add(tbodyTag.localName()); - elements.add(tdTag.localName()); - elements.add(tfootTag.localName()); - elements.add(thTag.localName()); - elements.add(theadTag.localName()); - elements.add(trTag.localName()); - elements.add(ulTag.localName()); - elements.add(xmpTag.localName()); + static NeverDestroyed<HashSet<AtomicString>> elements; + if (elements.get().isEmpty()) { + elements.get().add(addressTag.localName()); + elements.get().add(articleTag.localName()); + elements.get().add(asideTag.localName()); + elements.get().add(blockquoteTag.localName()); + elements.get().add(captionTag.localName()); + elements.get().add(centerTag.localName()); + elements.get().add(colTag.localName()); + elements.get().add(colgroupTag.localName()); + elements.get().add(ddTag.localName()); + elements.get().add(detailsTag.localName()); + elements.get().add(dirTag.localName()); + elements.get().add(divTag.localName()); + elements.get().add(dlTag.localName()); + elements.get().add(dtTag.localName()); + elements.get().add(fieldsetTag.localName()); + elements.get().add(figcaptionTag.localName()); + elements.get().add(figureTag.localName()); + elements.get().add(footerTag.localName()); + elements.get().add(formTag.localName()); + elements.get().add(h1Tag.localName()); + elements.get().add(h2Tag.localName()); + elements.get().add(h3Tag.localName()); + elements.get().add(h4Tag.localName()); + elements.get().add(h5Tag.localName()); + elements.get().add(h6Tag.localName()); + elements.get().add(headerTag.localName()); + elements.get().add(hgroupTag.localName()); + elements.get().add(hrTag.localName()); + elements.get().add(liTag.localName()); + elements.get().add(listingTag.localName()); + elements.get().add(mainTag.localName()); // Missing in the specification. + elements.get().add(menuTag.localName()); + elements.get().add(navTag.localName()); + elements.get().add(olTag.localName()); + elements.get().add(pTag.localName()); + elements.get().add(plaintextTag.localName()); + elements.get().add(preTag.localName()); + elements.get().add(sectionTag.localName()); + elements.get().add(summaryTag.localName()); + elements.get().add(tableTag.localName()); + elements.get().add(tbodyTag.localName()); + elements.get().add(tdTag.localName()); + elements.get().add(tfootTag.localName()); + elements.get().add(thTag.localName()); + elements.get().add(theadTag.localName()); + elements.get().add(trTag.localName()); + elements.get().add(ulTag.localName()); + elements.get().add(xmpTag.localName()); } - return elements.contains(name); + return elements.get().contains(name); } void ReplaceSelectionCommand::makeInsertedContentRoundTrippableWithHTMLTreeBuilder(InsertedNodes& insertedNodes) @@ -622,27 +633,34 @@ void ReplaceSelectionCommand::makeInsertedContentRoundTrippableWithHTMLTreeBuild RefPtr<Node> pastEndNode = insertedNodes.pastLastLeaf(); RefPtr<Node> next; for (RefPtr<Node> node = insertedNodes.firstNodeInserted(); node && node != pastEndNode; node = next) { - next = NodeTraversal::next(node.get()); + next = NodeTraversal::next(*node); - if (!node->isHTMLElement()) + if (!is<HTMLElement>(*node)) continue; - if (isProhibitedParagraphChild(toHTMLElement(node.get())->localName())) { - if (auto* paragraphElement = toHTMLElement(enclosingNodeWithTag(positionInParentBeforeNode(node.get()), pTag))) { + if (isProhibitedParagraphChild(downcast<HTMLElement>(*node).localName())) { + if (auto* paragraphElement = enclosingElementWithTag(positionInParentBeforeNode(node.get()), pTag)) { auto* parent = paragraphElement->parentNode(); if (parent && parent->hasEditableStyle()) - moveNodeOutOfAncestor(node, paragraphElement); + moveNodeOutOfAncestor(node, paragraphElement, insertedNodes); } } if (isHeaderElement(node.get())) { - if (HTMLElement* headerElement = toHTMLElement(highestEnclosingNodeOfType(positionInParentBeforeNode(node.get()), isHeaderElement))) - moveNodeOutOfAncestor(node, headerElement); + auto* headerElement = highestEnclosingNodeOfType(positionInParentBeforeNode(node.get()), isHeaderElement); + if (headerElement) { + if (headerElement->parentNode() && headerElement->parentNode()->isContentRichlyEditable()) + moveNodeOutOfAncestor(node, headerElement, insertedNodes); + else { + HTMLElement* newSpanElement = replaceElementWithSpanPreservingChildrenAndAttributes(downcast<HTMLElement>(node.get())); + insertedNodes.didReplaceNode(node.get(), newSpanElement); + } + } } } } -void ReplaceSelectionCommand::moveNodeOutOfAncestor(PassRefPtr<Node> prpNode, PassRefPtr<Node> prpAncestor) +void ReplaceSelectionCommand::moveNodeOutOfAncestor(PassRefPtr<Node> prpNode, PassRefPtr<Node> prpAncestor, InsertedNodes& insertedNodes) { RefPtr<Node> node = prpNode; RefPtr<Node> ancestor = prpAncestor; @@ -660,8 +678,10 @@ void ReplaceSelectionCommand::moveNodeOutOfAncestor(PassRefPtr<Node> prpNode, Pa removeNode(node); insertNodeBefore(node, nodeToSplitTo); } - if (!ancestor->firstChild()) - removeNode(ancestor.release()); + if (!ancestor->firstChild()) { + insertedNodes.willRemoveNode(ancestor.get()); + removeNode(WTFMove(ancestor)); + } } static inline bool hasRenderedText(const Text& text) @@ -674,9 +694,9 @@ void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds(InsertedNodes& ins document().updateLayoutIgnorePendingStylesheets(); Node* lastLeafInserted = insertedNodes.lastLeafInserted(); - if (lastLeafInserted && lastLeafInserted->isTextNode() && !hasRenderedText(toText(*lastLeafInserted)) - && !enclosingNodeWithTag(firstPositionInOrBeforeNode(lastLeafInserted), selectTag) - && !enclosingNodeWithTag(firstPositionInOrBeforeNode(lastLeafInserted), scriptTag)) { + if (is<Text>(lastLeafInserted) && !hasRenderedText(downcast<Text>(*lastLeafInserted)) + && !enclosingElementWithTag(firstPositionInOrBeforeNode(lastLeafInserted), selectTag) + && !enclosingElementWithTag(firstPositionInOrBeforeNode(lastLeafInserted), scriptTag)) { insertedNodes.willRemoveNode(lastLeafInserted); removeNode(lastLeafInserted); } @@ -684,7 +704,7 @@ void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds(InsertedNodes& ins // We don't have to make sure that firstNodeInserted isn't inside a select or script element // because it is a top level node in the fragment and the user can't insert into those elements. Node* firstNodeInserted = insertedNodes.firstNodeInserted(); - if (firstNodeInserted && firstNodeInserted->isTextNode() && !hasRenderedText(toText(*firstNodeInserted))) { + if (is<Text>(firstNodeInserted) && !hasRenderedText(downcast<Text>(*firstNodeInserted))) { insertedNodes.willRemoveNode(firstNodeInserted); removeNode(firstNodeInserted); } @@ -693,7 +713,7 @@ void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds(InsertedNodes& ins VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent() const { // FIXME: Why is this hack here? What's special about <select> tags? - Node* enclosingSelect = enclosingNodeWithTag(m_endOfInsertedContent, selectTag); + auto* enclosingSelect = enclosingElementWithTag(m_endOfInsertedContent, selectTag); return enclosingSelect ? lastPositionInOrAfterNode(enclosingSelect) : m_endOfInsertedContent; } @@ -712,7 +732,7 @@ static void removeHeadContents(ReplacementFragment& fragment) auto it = descendantsOfType<Element>(*fragment.fragment()).begin(); auto end = descendantsOfType<Element>(*fragment.fragment()).end(); while (it != end) { - if (it->hasTagName(baseTag) || it->hasTagName(linkTag) || it->hasTagName(metaTag) || it->hasTagName(styleTag) || isHTMLTitleElement(*it)) { + if (is<HTMLBaseElement>(*it) || is<HTMLLinkElement>(*it) || is<HTMLMetaElement>(*it) || is<HTMLStyleElement>(*it) || is<HTMLTitleElement>(*it)) { toRemove.append(&*it); it.traverseNextSkippingChildren(); continue; @@ -720,8 +740,8 @@ static void removeHeadContents(ReplacementFragment& fragment) ++it; } - for (unsigned i = 0; i < toRemove.size(); ++i) - fragment.removeNode(toRemove[i]); + for (auto& element : toRemove) + fragment.removeNode(element); } // Remove style spans before insertion if they are unnecessary. It's faster because we'll @@ -746,7 +766,7 @@ static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const // FIXME: This string comparison is a naive way of comparing two styles. // We should be taking the diff and check that the diff is empty. - if (styleText != toElement(wrappingStyleSpan)->getAttribute(styleAttr)) + if (styleText != downcast<Element>(*wrappingStyleSpan).getAttribute(styleAttr)) return false; fragment.removeNodePreservingChildren(wrappingStyleSpan); @@ -763,13 +783,13 @@ static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const // or at copy time. void ReplaceSelectionCommand::handleStyleSpans(InsertedNodes& insertedNodes) { - HTMLElement* wrappingStyleSpan = 0; + HTMLElement* wrappingStyleSpan = nullptr; // The style span that contains the source document's default style should be at // the top of the fragment, but Mail sometimes adds a wrapper (for Paste As Quotation), // so search for the top level style span instead of assuming it's at the top. - for (Node* node = insertedNodes.firstNodeInserted(); node; node = NodeTraversal::next(node)) { + for (Node* node = insertedNodes.firstNodeInserted(); node; node = NodeTraversal::next(*node)) { if (isLegacyAppleStyleSpan(node)) { - wrappingStyleSpan = toHTMLElement(node); + wrappingStyleSpan = downcast<HTMLElement>(node); break; } } @@ -831,7 +851,7 @@ void ReplaceSelectionCommand::mergeEndIfNeeded() // Merging forward could result in deleting the destination anchor node. // To avoid this, we add a placeholder node before the start of the paragraph. if (endOfParagraph(startOfParagraphToMove) == destination) { - RefPtr<Node> placeholder = createBreakElement(document()); + RefPtr<Node> placeholder = HTMLBRElement::create(document()); insertNodeBefore(placeholder, startOfParagraphToMove.deepEquivalent().deprecatedNode()); destination = VisiblePosition(positionBeforeNode(placeholder.get())); } @@ -852,11 +872,11 @@ void ReplaceSelectionCommand::mergeEndIfNeeded() static Node* enclosingInline(Node* node) { while (ContainerNode* parent = node->parentNode()) { - if (isBlockFlowElement(parent) || parent->hasTagName(bodyTag)) + if (isBlockFlowElement(*parent) || parent->hasTagName(bodyTag)) return node; // Stop if any previous sibling is a block. for (Node* sibling = node->previousSibling(); sibling; sibling = sibling->previousSibling()) { - if (isBlockFlowElement(sibling)) + if (isBlockFlowElement(*sibling)) return node; } node = parent; @@ -876,7 +896,7 @@ static bool isInlineNodeWithStyle(const Node* node) // We can skip over elements whose class attribute is // one of our internal classes. const HTMLElement* element = static_cast<const HTMLElement*>(node); - const AtomicString& classAttributeValue = element->getAttribute(classAttr); + const AtomicString& classAttributeValue = element->attributeWithoutSynchronization(classAttr); if (classAttributeValue == AppleTabSpanClass || classAttributeValue == AppleConvertedSpace || classAttributeValue == ApplePasteAsQuotation) @@ -891,25 +911,28 @@ inline Node* nodeToSplitToAvoidPastingIntoInlineNodesWithStyle(const Position& i return highestEnclosingNodeOfType(insertionPos, isInlineNodeWithStyle, CannotCrossEditingBoundary, containgBlock); } +bool ReplaceSelectionCommand::willApplyCommand() +{ + ensureReplacementFragment(); + m_documentFragmentPlainText = m_documentFragment->textContent(); + m_documentFragmentHTMLMarkup = createMarkup(*m_documentFragment); + return CompositeEditCommand::willApplyCommand(); +} + void ReplaceSelectionCommand::doApply() { VisibleSelection selection = endingSelection(); ASSERT(selection.isCaretOrRange()); ASSERT(selection.start().deprecatedNode()); - if (!selection.isNonOrphanedCaretOrRange() || !selection.start().deprecatedNode()) + if (selection.isNoneOrOrphaned() || !selection.start().deprecatedNode() || !selection.isContentEditable()) return; - if (!selection.rootEditableElement()) - return; - -#if PLATFORM(IOS) // In plain text only regions, we create style-less fragments, so the inserted content will automatically // match the style of the surrounding area and so we can avoid unnecessary work below for m_matchStyle. if (!selection.isContentRichlyEditable()) m_matchStyle = false; -#endif - ReplacementFragment fragment(document(), m_documentFragment.get(), selection); + ReplacementFragment& fragment = *ensureReplacementFragment(); if (performTrivialReplace(fragment)) return; @@ -932,12 +955,12 @@ void ReplaceSelectionCommand::doApply() Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().deprecatedNode()); Position insertionPos = selection.start(); - bool startIsInsideMailBlockquote = enclosingNodeOfType(insertionPos, isMailBlockquote, CanCrossEditingBoundary); + bool shouldHandleMailBlockquote = enclosingNodeOfType(insertionPos, isMailBlockquote, CanCrossEditingBoundary) && !m_ignoreMailBlockquote; bool selectionIsPlainText = !selection.isContentRichlyEditable(); Element* currentRoot = selection.rootEditableElement(); - if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !startIsInsideMailBlockquote) || - startBlock == currentRoot || isListItem(startBlock) || selectionIsPlainText) + if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !shouldHandleMailBlockquote) + || startBlock == currentRoot || isListItem(startBlock) || selectionIsPlainText) m_preventNesting = false; if (selection.isRange()) { @@ -947,10 +970,11 @@ void ReplaceSelectionCommand::doApply() // will leave hanging block(s). // Merge blocks if the start of the selection was in a Mail blockquote, since we handle // that case specially to prevent nesting. - bool mergeBlocksAfterDelete = startIsInsideMailBlockquote || isEndOfParagraph(visibleEnd) || isStartOfBlock(visibleStart); + bool mergeBlocksAfterDelete = shouldHandleMailBlockquote || isEndOfParagraph(visibleEnd) || isStartOfBlock(visibleStart); // FIXME: We should only expand to include fully selected special elements if we are copying a // selection and pasting it on top of itself. - deleteSelection(false, mergeBlocksAfterDelete, true, false); + // FIXME: capturing the content of this delete would allow a replace accessibility notification instead of a simple insert + deleteSelection(false, mergeBlocksAfterDelete, true, false, true); visibleStart = endingSelection().visibleStart(); if (fragment.hasInterchangeNewlineAtStart()) { if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) { @@ -976,7 +1000,7 @@ void ReplaceSelectionCommand::doApply() // As long as the div styles are the same, visually you'd expect: <div>xbar</div><div>bar</div><div>bazx</div>, // not <div>xbar<div>bar</div><div>bazx</div></div>. // Don't do this if the selection started in a Mail blockquote. - if (m_preventNesting && !startIsInsideMailBlockquote && !isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) { + if (m_preventNesting && !shouldHandleMailBlockquote && !isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) { insertParagraphSeparator(); setEndingSelection(endingSelection().visibleStart().previous()); } @@ -986,7 +1010,7 @@ void ReplaceSelectionCommand::doApply() // We don't want any of the pasted content to end up nested in a Mail blockquote, so first break // out of any surrounding Mail blockquotes. Unless we're inserting in a table, in which case // breaking the blockquote will prevent the content from actually being inserted in the table. - if (startIsInsideMailBlockquote && m_preventNesting && !(enclosingNodeOfType(insertionPos, &isTableStructureNode))) { + if (shouldHandleMailBlockquote && m_preventNesting && !(enclosingNodeOfType(insertionPos, &isTableStructureNode))) { applyCommandToComposite(BreakBlockquoteCommand::create(document())); // This will leave a br between the split. Node* br = endingSelection().start().deprecatedNode(); @@ -1015,7 +1039,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 && insertionBlock && !isTableCell(insertionBlock.get()) && !startIsInsideMailBlockquote) { + if (m_preventNesting && insertionBlock && !isTableCell(insertionBlock.get()) && !shouldHandleMailBlockquote) { ASSERT(insertionBlock != currentRoot); VisiblePosition visibleInsertionPos(insertionPos); if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInsertionPos) && fragment.hasInterchangeNewlineAtEnd())) @@ -1031,8 +1055,6 @@ void ReplaceSelectionCommand::doApply() // any work performed after this that queries or uses the typing style. frame().selection().clearTypingStyle(); - removeHeadContents(fragment); - // We don't want the destination to end up inside nodes that weren't selected. To avoid that, we move the // position forward without changing the visible position so we're still at the same visible location, but // outside of preceding tags. @@ -1090,16 +1112,16 @@ void ReplaceSelectionCommand::doApply() fragment.removeNode(refNode); Node* blockStart = enclosingBlock(insertionPos.deprecatedNode()); - if ((isListElement(refNode.get()) || (isLegacyAppleStyleSpan(refNode.get()) && isListElement(refNode->firstChild()))) + if ((isListHTMLElement(refNode.get()) || (isLegacyAppleStyleSpan(refNode.get()) && isListHTMLElement(refNode->firstChild()))) && blockStart && blockStart->renderer()->isListItem()) - refNode = insertAsListItems(toHTMLElement(refNode.get()), blockStart, insertionPos, insertedNodes); + refNode = insertAsListItems(downcast<HTMLElement>(refNode.get()), blockStart, insertionPos, insertedNodes); else { insertNodeAt(refNode, insertionPos); insertedNodes.respondToNodeInsertion(refNode.get()); } // Mutation events (bug 22634) may have already removed the inserted content - if (!refNode->inDocument()) + if (!refNode->isConnected()) return; bool plainTextFragment = isPlainTextMarkup(refNode.get()); @@ -1111,7 +1133,7 @@ void ReplaceSelectionCommand::doApply() insertedNodes.respondToNodeInsertion(node.get()); // Mutation events (bug 22634) may have already removed the inserted content - if (!node->inDocument()) + if (!node->isConnected()) return; refNode = node; @@ -1126,15 +1148,15 @@ void ReplaceSelectionCommand::doApply() handleStyleSpans(insertedNodes); // Mutation events (bug 20161) may have already removed the inserted content - if (!insertedNodes.firstNodeInserted() || !insertedNodes.firstNodeInserted()->inDocument()) + if (!insertedNodes.firstNodeInserted() || !insertedNodes.firstNodeInserted()->isConnected()) return; VisiblePosition startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNodeInserted()); // We inserted before the insertionBlock to prevent nesting, and the content before the insertionBlock wasn't in its own block and // didn't have a br after it, so the inserted content ended up in the same paragraph. - if (insertionBlock && insertionPos.deprecatedNode() == insertionBlock->parentNode() && (unsigned)insertionPos.deprecatedEditingOffset() < insertionBlock->nodeIndex() && !isStartOfParagraph(startOfInsertedContent)) - insertNodeAt(createBreakElement(document()), startOfInsertedContent.deepEquivalent()); + if (!startOfInsertedContent.isNull() && insertionBlock && insertionPos.deprecatedNode() == insertionBlock->parentNode() && (unsigned)insertionPos.deprecatedEditingOffset() < insertionBlock->computeNodeIndex() && !isStartOfParagraph(startOfInsertedContent)) + insertNodeAt(HTMLBRElement::create(document()), startOfInsertedContent.deepEquivalent()); if (endBR && (plainTextFragment || shouldRemoveEndBR(endBR.get(), originalVisPosBeforeEndBR))) { RefPtr<Node> parent = endBR->parentNode(); @@ -1161,7 +1183,7 @@ void ReplaceSelectionCommand::doApply() // the start merge so that the start merge doesn't effect our decision. m_shouldMergeEnd = shouldMergeEnd(selectionEndWasEndOfParagraph); - if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasInterchangeNewlineAtStart(), startIsInsideMailBlockquote)) { + if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasInterchangeNewlineAtStart(), shouldHandleMailBlockquote)) { VisiblePosition startOfParagraphToMove = positionAtStartOfInsertedContent(); VisiblePosition destination = startOfParagraphToMove.previous(); // We need to handle the case where we need to merge the end @@ -1169,7 +1191,7 @@ void ReplaceSelectionCommand::doApply() // We insert a placeholder before the newly inserted content to avoid being merged into the inline. Node* destinationNode = destination.deepEquivalent().deprecatedNode(); if (m_shouldMergeEnd && destinationNode != enclosingInline(destinationNode) && enclosingInline(destinationNode)->nextSibling()) - insertNodeBefore(createBreakElement(document()), refNode.get()); + insertNodeBefore(HTMLBRElement::create(document()), refNode.get()); // Merging the the first paragraph of inserted content with the content that came // before the selection that was pasted into would also move content after @@ -1180,9 +1202,9 @@ void ReplaceSelectionCommand::doApply() // comes after and prevent that from happening. VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent(); if (startOfParagraph(endOfInsertedContent) == startOfParagraphToMove) { - insertNodeAt(createBreakElement(document()), endOfInsertedContent.deepEquivalent()); + insertNodeAt(HTMLBRElement::create(document()), endOfInsertedContent.deepEquivalent()); // Mutation events (bug 22634) triggered by inserting the <br> might have removed the content we're about to move - if (!startOfParagraphToMove.deepEquivalent().anchorNode()->inDocument()) + if (!startOfParagraphToMove.deepEquivalent().anchorNode()->isConnected()) return; } @@ -1204,13 +1226,13 @@ void ReplaceSelectionCommand::doApply() setEndingSelection(endOfInsertedContent); Node* enclosingNode = enclosingBlock(endOfInsertedContent.deepEquivalent().deprecatedNode()); if (isListItem(enclosingNode)) { - RefPtr<Node> newListItem = createListItemElement(document()); - insertNodeAfter(newListItem, enclosingNode); - setEndingSelection(VisiblePosition(firstPositionInNode(newListItem.get()))); + auto newListItem = HTMLLIElement::create(document()); + insertNodeAfter(newListItem.copyRef(), enclosingNode); + setEndingSelection(VisiblePosition(firstPositionInNode(newListItem.ptr()))); } 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, !startIsInsideMailBlockquote && highestEnclosingNodeOfType(endOfInsertedContent.deepEquivalent(), + insertParagraphSeparator(true, !shouldHandleMailBlockquote && highestEnclosingNodeOfType(endOfInsertedContent.deepEquivalent(), isMailBlockquote, CannotCrossEditingBoundary, insertedNodes.firstNodeInserted()->parentNode())); } @@ -1227,7 +1249,7 @@ void ReplaceSelectionCommand::doApply() mergeEndIfNeeded(); if (Node* mailBlockquote = enclosingNodeOfType(positionAtStartOfInsertedContent().deepEquivalent(), isMailPasteAsQuotationNode)) - removeNodeAttribute(toElement(mailBlockquote), classAttr); + removeNodeAttribute(downcast<Element>(mailBlockquote), classAttr); if (shouldPerformSmartReplace()) addSpacesForSmartReplace(); @@ -1240,11 +1262,27 @@ void ReplaceSelectionCommand::doApply() completeHTMLReplacement(lastPositionToSelect); } +String ReplaceSelectionCommand::inputEventData() const +{ + if (isEditingTextAreaOrTextInput()) + return m_documentFragment->textContent(); + + return CompositeEditCommand::inputEventData(); +} + +RefPtr<DataTransfer> ReplaceSelectionCommand::inputEventDataTransfer() const +{ + if (isEditingTextAreaOrTextInput()) + return CompositeEditCommand::inputEventDataTransfer(); + + return DataTransfer::createForInputEvent(m_documentFragmentPlainText, m_documentFragmentHTMLMarkup); +} + bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR, const VisiblePosition& originalVisPosBeforeEndBR) { - if (!endBR || !endBR->inDocument()) + if (!endBR || !endBR->isConnected()) return false; - + VisiblePosition visiblePos(positionBeforeNode(endBR)); // Don't remove the br if nothing was inserted. @@ -1266,7 +1304,7 @@ bool ReplaceSelectionCommand::shouldPerformSmartReplace() const return false; Element* textControl = enclosingTextFormControl(positionAtStartOfInsertedContent().deepEquivalent()); - if (textControl && isHTMLInputElement(textControl) && toHTMLInputElement(textControl)->isPasswordField()) + if (is<HTMLInputElement>(textControl) && downcast<HTMLInputElement>(*textControl).isPasswordField()) return false; // Disable smart replace for password fields. return true; @@ -1284,7 +1322,7 @@ void ReplaceSelectionCommand::addSpacesForSmartReplace() Position endUpstream = endOfInsertedContent.deepEquivalent().upstream(); Node* endNode = endUpstream.computeNodeBeforePosition(); - int endOffset = endNode && endNode->isTextNode() ? toText(endNode)->length() : 0; + int endOffset = is<Text>(endNode) ? downcast<Text>(*endNode).length() : 0; if (endUpstream.anchorType() == Position::PositionIsOffsetInAnchor) { endNode = endUpstream.containerNode(); endOffset = endUpstream.offsetInContainerNode(); @@ -1293,8 +1331,8 @@ void ReplaceSelectionCommand::addSpacesForSmartReplace() bool needsTrailingSpace = !isEndOfParagraph(endOfInsertedContent) && !isCharacterSmartReplaceExemptConsideringNonBreakingSpace(endOfInsertedContent.characterAfter(), false); if (needsTrailingSpace && endNode) { bool collapseWhiteSpace = !endNode->renderer() || endNode->renderer()->style().collapseWhiteSpace(); - if (endNode->isTextNode()) { - insertTextIntoNode(toText(endNode), endOffset, collapseWhiteSpace ? nonBreakingSpaceString() : " "); + if (is<Text>(*endNode)) { + insertTextIntoNode(downcast<Text>(endNode), endOffset, collapseWhiteSpace ? nonBreakingSpaceString() : " "); if (m_endOfInsertedContent.containerNode() == endNode) m_endOfInsertedContent.moveToOffset(m_endOfInsertedContent.offsetInContainerNode() + 1); } else { @@ -1317,8 +1355,8 @@ void ReplaceSelectionCommand::addSpacesForSmartReplace() bool needsLeadingSpace = !isStartOfParagraph(startOfInsertedContent) && !isCharacterSmartReplaceExemptConsideringNonBreakingSpace(startOfInsertedContent.previous().characterAfter(), true); if (needsLeadingSpace && startNode) { bool collapseWhiteSpace = !startNode->renderer() || startNode->renderer()->style().collapseWhiteSpace(); - if (startNode->isTextNode()) { - insertTextIntoNode(toText(startNode), startOffset, collapseWhiteSpace ? nonBreakingSpaceString() : " "); + if (is<Text>(*startNode)) { + insertTextIntoNode(downcast<Text>(startNode), startOffset, collapseWhiteSpace ? nonBreakingSpaceString() : " "); if (m_endOfInsertedContent.containerNode() == startNode && m_endOfInsertedContent.offsetInContainerNode()) m_endOfInsertedContent.moveToOffset(m_endOfInsertedContent.offsetInContainerNode() + 1); } else { @@ -1357,6 +1395,9 @@ void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositi else return; + if (AXObjectCache::accessibilityEnabled() && editingAction() == EditActionPaste) + m_visibleSelectionForInsertedText = VisibleSelection(start, end); + if (m_selectReplacement) setEndingSelection(VisibleSelection(start, end, SEL_DEFAULT_AFFINITY, endingSelection().isDirectional())); else @@ -1367,24 +1408,24 @@ void ReplaceSelectionCommand::mergeTextNodesAroundPosition(Position& position, P { bool positionIsOffsetInAnchor = position.anchorType() == Position::PositionIsOffsetInAnchor; bool positionOnlyToBeUpdatedIsOffsetInAnchor = positionOnlyToBeUpdated.anchorType() == Position::PositionIsOffsetInAnchor; - RefPtr<Text> text = 0; - if (positionIsOffsetInAnchor && position.containerNode() && position.containerNode()->isTextNode()) - text = toText(position.containerNode()); + RefPtr<Text> text; + if (positionIsOffsetInAnchor && is<Text>(position.containerNode())) + text = downcast<Text>(position.containerNode()); else { Node* before = position.computeNodeBeforePosition(); - if (before && before->isTextNode()) - text = toText(before); + if (is<Text>(before)) + text = downcast<Text>(before); else { Node* after = position.computeNodeAfterPosition(); - if (after && after->isTextNode()) - text = toText(after); + if (is<Text>(after)) + text = downcast<Text>(after); } } if (!text) return; - if (text->previousSibling() && text->previousSibling()->isTextNode()) { - RefPtr<Text> previous = toText(text->previousSibling()); + if (is<Text>(text->previousSibling())) { + Ref<Text> previous(downcast<Text>(*text->previousSibling())); insertTextIntoNode(text, 0, previous->data()); if (positionIsOffsetInAnchor) @@ -1395,43 +1436,38 @@ void ReplaceSelectionCommand::mergeTextNodesAroundPosition(Position& position, P if (positionOnlyToBeUpdatedIsOffsetInAnchor) { if (positionOnlyToBeUpdated.containerNode() == text) positionOnlyToBeUpdated.moveToOffset(previous->length() + positionOnlyToBeUpdated.offsetInContainerNode()); - else if (positionOnlyToBeUpdated.containerNode() == previous) - positionOnlyToBeUpdated.moveToPosition(text, positionOnlyToBeUpdated.offsetInContainerNode()); + else if (positionOnlyToBeUpdated.containerNode() == previous.ptr()) + positionOnlyToBeUpdated.moveToPosition(text.get(), positionOnlyToBeUpdated.offsetInContainerNode()); } else updatePositionForNodeRemoval(positionOnlyToBeUpdated, previous.get()); - removeNode(previous); + removeNode(previous.ptr()); } - if (text->nextSibling() && text->nextSibling()->isTextNode()) { - RefPtr<Text> next = toText(text->nextSibling()); + if (is<Text>(text->nextSibling())) { + Ref<Text> next(downcast<Text>(*text->nextSibling())); unsigned originalLength = text->length(); insertTextIntoNode(text, originalLength, next->data()); if (!positionIsOffsetInAnchor) updatePositionForNodeRemoval(position, next.get()); - if (positionOnlyToBeUpdatedIsOffsetInAnchor && positionOnlyToBeUpdated.containerNode() == next) - positionOnlyToBeUpdated.moveToPosition(text, originalLength + positionOnlyToBeUpdated.offsetInContainerNode()); + if (positionOnlyToBeUpdatedIsOffsetInAnchor && positionOnlyToBeUpdated.containerNode() == next.ptr()) + positionOnlyToBeUpdated.moveToPosition(text.get(), originalLength + positionOnlyToBeUpdated.offsetInContainerNode()); else updatePositionForNodeRemoval(positionOnlyToBeUpdated, next.get()); - removeNode(next); + removeNode(next.ptr()); } } -EditAction ReplaceSelectionCommand::editingAction() const -{ - return m_editAction; -} - // If the user is inserting a list into an existing list, instead of nesting the list, // we put the list items into the existing list. Node* ReplaceSelectionCommand::insertAsListItems(PassRefPtr<HTMLElement> prpListElement, Node* insertionBlock, const Position& insertPos, InsertedNodes& insertedNodes) { RefPtr<HTMLElement> listElement = prpListElement; - while (listElement->hasChildNodes() && isListElement(listElement->firstChild()) && listElement->childNodeCount() == 1) - listElement = toHTMLElement(listElement->firstChild()); + while (listElement->hasOneChild() && isListHTMLElement(listElement->firstChild())) + listElement = downcast<HTMLElement>(listElement->firstChild()); bool isStart = isStartOfParagraph(insertPos); bool isEnd = isEndOfParagraph(insertPos); @@ -1442,13 +1478,13 @@ Node* ReplaceSelectionCommand::insertAsListItems(PassRefPtr<HTMLElement> prpList // list items and insert these nodes between them. if (isMiddle) { int textNodeOffset = insertPos.offsetInContainerNode(); - if (insertPos.deprecatedNode()->isTextNode() && textNodeOffset > 0) - splitTextNode(toText(insertPos.deprecatedNode()), textNodeOffset); + if (is<Text>(*insertPos.deprecatedNode()) && textNodeOffset > 0) + splitTextNode(downcast<Text>(insertPos.deprecatedNode()), textNodeOffset); splitTreeToNode(insertPos.deprecatedNode(), lastNode, true); } while (RefPtr<Node> listItem = listElement->firstChild()) { - listElement->removeChild(listItem.get(), ASSERT_NO_EXCEPTION); + listElement->removeChild(*listItem); if (isStart || isMiddle) { insertNodeBefore(listItem, lastNode); insertedNodes.respondToNodeInsertion(listItem.get()); @@ -1459,7 +1495,7 @@ Node* ReplaceSelectionCommand::insertAsListItems(PassRefPtr<HTMLElement> prpList } else ASSERT_NOT_REACHED(); } - if (isStart || isMiddle) + if ((isStart || isMiddle) && lastNode->previousSibling()) lastNode = lastNode->previousSibling(); return lastNode; } @@ -1475,13 +1511,23 @@ void ReplaceSelectionCommand::updateNodesInserted(Node *node) m_endOfInsertedContent = lastPositionInOrAfterNode(node->lastDescendant()); } +ReplacementFragment* ReplaceSelectionCommand::ensureReplacementFragment() +{ + if (!m_replacementFragment) { + m_replacementFragment = std::make_unique<ReplacementFragment>(document(), m_documentFragment.get(), endingSelection()); + removeHeadContents(*m_replacementFragment); + } + + return m_replacementFragment.get(); +} + // During simple pastes, where we're just pasting a text node into a run of text, we insert the text node // directly into the text node that holds the selection. This is much faster than the generalized code in // ReplaceSelectionCommand, and works around <https://bugs.webkit.org/show_bug.cgi?id=6148> since we don't // split text nodes. bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment& fragment) { - if (!fragment.firstChild() || fragment.firstChild() != fragment.lastChild() || !fragment.firstChild()->isTextNode()) + if (!is<Text>(fragment.firstChild()) || fragment.firstChild() != fragment.lastChild()) return false; // FIXME: Would be nice to handle smart replace in the fast path. @@ -1493,11 +1539,11 @@ bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment& f return false; RefPtr<Node> nodeAfterInsertionPos = endingSelection().end().downstream().anchorNode(); - Text* textNode = toText(fragment.firstChild()); + Text& textNode = downcast<Text>(*fragment.firstChild()); // Our fragment creation code handles tabs, spaces, and newlines, so we don't have to worry about those here. Position start = endingSelection().start(); - Position end = replaceSelectedTextInNode(textNode->data()); + Position end = replaceSelectedTextInNode(textNode.data()); if (end.isNull()) return false; @@ -1507,6 +1553,9 @@ bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment& f VisibleSelection selectionAfterReplace(m_selectReplacement ? start : end, end); + if (AXObjectCache::accessibilityEnabled() && editingAction() == EditActionPaste) + m_visibleSelectionForInsertedText = VisibleSelection(start, end); + setEndingSelection(selectionAfterReplace); return true; diff --git a/Source/WebCore/editing/ReplaceSelectionCommand.h b/Source/WebCore/editing/ReplaceSelectionCommand.h index f46d829fb..3d2f419ec 100644 --- a/Source/WebCore/editing/ReplaceSelectionCommand.h +++ b/Source/WebCore/editing/ReplaceSelectionCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ReplaceSelectionCommand_h -#define ReplaceSelectionCommand_h +#pragma once #include "CompositeEditCommand.h" #include "NodeTraversal.h" @@ -42,22 +41,27 @@ public: MatchStyle = 1 << 2, PreventNesting = 1 << 3, MovingParagraph = 1 << 4, - SanitizeFragment = 1 << 5 + SanitizeFragment = 1 << 5, + IgnoreMailBlockquote = 1 << 6, }; typedef unsigned CommandOptions; - static PassRefPtr<ReplaceSelectionCommand> create(Document& document, PassRefPtr<DocumentFragment> fragment, CommandOptions options, EditAction action = EditActionPaste) + static Ref<ReplaceSelectionCommand> create(Document& document, RefPtr<DocumentFragment>&& fragment, CommandOptions options, EditAction editingAction = EditActionInsert) { - return adoptRef(new ReplaceSelectionCommand(document, fragment, options, action)); + return adoptRef(*new ReplaceSelectionCommand(document, WTFMove(fragment), options, editingAction)); } + VisibleSelection visibleSelectionForInsertedText() const { return m_visibleSelectionForInsertedText; } + private: - ReplaceSelectionCommand(Document&, PassRefPtr<DocumentFragment>, CommandOptions, EditAction); + ReplaceSelectionCommand(Document&, RefPtr<DocumentFragment>&&, CommandOptions, EditAction); + + String inputEventData() const final; + RefPtr<DataTransfer> inputEventDataTransfer() const final; + bool willApplyCommand() final; + void doApply() override; - virtual void doApply(); - virtual EditAction editingAction() const; - class InsertedNodes { public: void respondToNodeInsertion(Node*); @@ -67,7 +71,14 @@ private: Node* firstNodeInserted() const { return m_firstNodeInserted.get(); } Node* lastLeafInserted() const { return m_lastNodeInserted->lastDescendant(); } - Node* pastLastLeaf() const { return m_lastNodeInserted ? NodeTraversal::next(lastLeafInserted()) : 0; } + Node* pastLastLeaf() const + { + if (m_lastNodeInserted) { + ASSERT(lastLeafInserted()); + return NodeTraversal::next(*lastLeafInserted()); + } + return nullptr; + } private: RefPtr<Node> m_firstNodeInserted; @@ -89,7 +100,7 @@ private: void removeRedundantStylesAndKeepStyleSpanInline(InsertedNodes&); void makeInsertedContentRoundTrippableWithHTMLTreeBuilder(InsertedNodes&); - void moveNodeOutOfAncestor(PassRefPtr<Node>, PassRefPtr<Node> ancestor); + void moveNodeOutOfAncestor(PassRefPtr<Node>, PassRefPtr<Node> ancestor, InsertedNodes&); void handleStyleSpans(InsertedNodes&); void handlePasteAsQuotationNode(); @@ -101,8 +112,10 @@ private: void completeHTMLReplacement(const Position& lastPositionToSelect); void mergeTextNodesAroundPosition(Position&, Position& positionOnlyToBeUpdated); + ReplacementFragment* ensureReplacementFragment(); bool performTrivialReplace(const ReplacementFragment&); + VisibleSelection m_visibleSelectionForInsertedText; Position m_startOfInsertedContent; Position m_endOfInsertedContent; RefPtr<EditingStyle> m_insertionStyle; @@ -110,13 +123,14 @@ private: bool m_smartReplace; bool m_matchStyle; RefPtr<DocumentFragment> m_documentFragment; + std::unique_ptr<ReplacementFragment> m_replacementFragment; + String m_documentFragmentHTMLMarkup; + String m_documentFragmentPlainText; bool m_preventNesting; bool m_movingParagraph; - EditAction m_editAction; bool m_sanitizeFragment; bool m_shouldMergeEnd; + bool m_ignoreMailBlockquote; }; } // namespace WebCore - -#endif // ReplaceSelectionCommand_h diff --git a/Source/WebCore/editing/SelectionRectGatherer.cpp b/Source/WebCore/editing/SelectionRectGatherer.cpp new file mode 100644 index 000000000..fd6b24333 --- /dev/null +++ b/Source/WebCore/editing/SelectionRectGatherer.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 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. AND ITS 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 APPLE INC. OR ITS 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 "SelectionRectGatherer.h" + +#if ENABLE(SERVICE_CONTROLS) + +#include "Editor.h" +#include "EditorClient.h" +#include "Frame.h" +#include "MainFrame.h" +#include "RenderView.h" +#include "ServicesOverlayController.h" + +namespace WebCore { + +SelectionRectGatherer::SelectionRectGatherer(RenderView& renderView) + : m_renderView(renderView) + , m_isTextOnly(true) +{ +} + +void SelectionRectGatherer::addRect(RenderLayerModelObject *repaintContainer, const LayoutRect& rect) +{ + if (!rect.isEmpty()) { + if (repaintContainer) + m_rects.append(LayoutRect(repaintContainer->localToAbsoluteQuad(FloatQuad(rect)).boundingBox())); + else + m_rects.append(rect); + } +} + +void SelectionRectGatherer::addGapRects(RenderLayerModelObject *repaintContainer, const GapRects& rects) +{ + if (repaintContainer) { + GapRects absoluteGapRects; + absoluteGapRects.uniteLeft(LayoutRect(repaintContainer->localToAbsoluteQuad(FloatQuad(rects.left())).boundingBox())); + absoluteGapRects.uniteCenter(LayoutRect(repaintContainer->localToAbsoluteQuad(FloatQuad(rects.center())).boundingBox())); + absoluteGapRects.uniteRight(LayoutRect(repaintContainer->localToAbsoluteQuad(FloatQuad(rects.right())).boundingBox())); + m_gapRects.append(absoluteGapRects); + } else + m_gapRects.append(rects); +} + +SelectionRectGatherer::Notifier::Notifier(SelectionRectGatherer& gatherer) + : m_gatherer(gatherer) +{ +} + +SelectionRectGatherer::Notifier::~Notifier() +{ + m_gatherer.m_renderView.view().frame().mainFrame().servicesOverlayController().selectionRectsDidChange(m_gatherer.m_rects, m_gatherer.m_gapRects, m_gatherer.isTextOnly()); +} + +std::unique_ptr<SelectionRectGatherer::Notifier> SelectionRectGatherer::clearAndCreateNotifier() +{ + m_rects.clear(); + m_gapRects.clear(); + m_isTextOnly = true; + + return std::make_unique<Notifier>(*this); +} + +} // namespace WebCore + +#endif // ENABLE(SERVICE_CONTROLS) diff --git a/Source/WebCore/editing/SelectionRectGatherer.h b/Source/WebCore/editing/SelectionRectGatherer.h new file mode 100644 index 000000000..047cf16f0 --- /dev/null +++ b/Source/WebCore/editing/SelectionRectGatherer.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 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. AND ITS 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 APPLE INC. OR ITS 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. + */ + +#pragma once + +#if ENABLE(SERVICE_CONTROLS) + +#include <wtf/Noncopyable.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class LayoutRect; +class RenderLayerModelObject; +class RenderView; + +struct GapRects; + +class SelectionRectGatherer { + WTF_MAKE_NONCOPYABLE(SelectionRectGatherer); + +public: + SelectionRectGatherer(RenderView&); + + void addRect(RenderLayerModelObject *repaintContainer, const LayoutRect&); + void addGapRects(RenderLayerModelObject *repaintContainer, const GapRects&); + void setTextOnly(bool isTextOnly) { m_isTextOnly = isTextOnly; } + bool isTextOnly() const { return m_isTextOnly; } + + class Notifier { + WTF_MAKE_NONCOPYABLE(Notifier); + public: + Notifier(SelectionRectGatherer&); + ~Notifier(); + + private: + SelectionRectGatherer& m_gatherer; + }; + + std::unique_ptr<Notifier> clearAndCreateNotifier(); + +private: + RenderView& m_renderView; + + // All rects are in RenderView coordinates. + Vector<LayoutRect> m_rects; + Vector<GapRects> m_gapRects; + bool m_isTextOnly; +}; + +} // namespace WebCore + +#endif // ENABLE(SERVICE_CONTROLS) diff --git a/Source/WebCore/editing/SetNodeAttributeCommand.cpp b/Source/WebCore/editing/SetNodeAttributeCommand.cpp index 113e209ab..1950f0f58 100644 --- a/Source/WebCore/editing/SetNodeAttributeCommand.cpp +++ b/Source/WebCore/editing/SetNodeAttributeCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -49,7 +49,7 @@ void SetNodeAttributeCommand::doApply() void SetNodeAttributeCommand::doUnapply() { m_element->setAttribute(m_attribute, m_oldValue); - AtomicStringImpl* nullString = 0; + AtomicStringImpl* nullString = nullptr; m_oldValue = nullString; } diff --git a/Source/WebCore/editing/SetNodeAttributeCommand.h b/Source/WebCore/editing/SetNodeAttributeCommand.h index acda7c4da..f3ae409be 100644 --- a/Source/WebCore/editing/SetNodeAttributeCommand.h +++ b/Source/WebCore/editing/SetNodeAttributeCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SetNodeAttributeCommand_h -#define SetNodeAttributeCommand_h +#pragma once #include "EditCommand.h" #include "QualifiedName.h" @@ -33,19 +32,19 @@ namespace WebCore { class SetNodeAttributeCommand : public SimpleEditCommand { public: - static PassRefPtr<SetNodeAttributeCommand> create(PassRefPtr<Element> element, const QualifiedName& attribute, const AtomicString& value) + static Ref<SetNodeAttributeCommand> create(PassRefPtr<Element> element, const QualifiedName& attribute, const AtomicString& value) { - return adoptRef(new SetNodeAttributeCommand(element, attribute, value)); + return adoptRef(*new SetNodeAttributeCommand(element, attribute, value)); } private: SetNodeAttributeCommand(PassRefPtr<Element>, const QualifiedName& attribute, const AtomicString& value); - virtual void doApply() override; - virtual void doUnapply() override; + void doApply() override; + void doUnapply() override; #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override; + void getNodesInCommand(HashSet<Node*>&) override; #endif RefPtr<Element> m_element; @@ -55,5 +54,3 @@ private: }; } // namespace WebCore - -#endif // SetNodeAttributeCommand_h diff --git a/Source/WebCore/editing/SetSelectionCommand.cpp b/Source/WebCore/editing/SetSelectionCommand.cpp index f153a8d44..00a4d561d 100644 --- a/Source/WebCore/editing/SetSelectionCommand.cpp +++ b/Source/WebCore/editing/SetSelectionCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -42,7 +42,7 @@ void SetSelectionCommand::doApply() { FrameSelection& selection = frame().selection(); - if (selection.shouldChangeSelection(m_selectionToSet) && m_selectionToSet.isNonOrphanedCaretOrRange()) { + if (selection.shouldChangeSelection(m_selectionToSet) && !m_selectionToSet.isNoneOrOrphaned()) { selection.setSelection(m_selectionToSet, m_options); setEndingSelection(m_selectionToSet); } @@ -52,7 +52,7 @@ void SetSelectionCommand::doUnapply() { FrameSelection& selection = frame().selection(); - if (selection.shouldChangeSelection(startingSelection()) && startingSelection().isNonOrphanedCaretOrRange()) + if (selection.shouldChangeSelection(startingSelection()) && !startingSelection().isNoneOrOrphaned()) selection.setSelection(startingSelection(), m_options); } diff --git a/Source/WebCore/editing/SetSelectionCommand.h b/Source/WebCore/editing/SetSelectionCommand.h index 229eb54f7..897c4d9ac 100644 --- a/Source/WebCore/editing/SetSelectionCommand.h +++ b/Source/WebCore/editing/SetSelectionCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SetSelectionCommand_h -#define SetSelectionCommand_h +#pragma once #include "EditCommand.h" #include "FrameSelection.h" @@ -33,19 +32,19 @@ namespace WebCore { class SetSelectionCommand : public SimpleEditCommand { public: - static PassRefPtr<SetSelectionCommand> create(const VisibleSelection& selection, FrameSelection::SetSelectionOptions options) + static Ref<SetSelectionCommand> create(const VisibleSelection& selection, FrameSelection::SetSelectionOptions options) { - return adoptRef(new SetSelectionCommand(selection, options)); + return adoptRef(*new SetSelectionCommand(selection, options)); } private: SetSelectionCommand(const VisibleSelection&, FrameSelection::SetSelectionOptions); - virtual void doApply() override; - virtual void doUnapply() override; + void doApply() override; + void doUnapply() override; #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override { } + void getNodesInCommand(HashSet<Node*>&) override { } #endif FrameSelection::SetSelectionOptions m_options; @@ -53,5 +52,3 @@ private: }; } // namespace WebCore - -#endif // SetSelectionCommand_h diff --git a/Source/WebCore/editing/SimplifyMarkupCommand.cpp b/Source/WebCore/editing/SimplifyMarkupCommand.cpp index 4106d8f6c..31570a5de 100644 --- a/Source/WebCore/editing/SimplifyMarkupCommand.cpp +++ b/Source/WebCore/editing/SimplifyMarkupCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Apple Computer, Inc. All rights reserved. + * 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 @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -52,16 +52,16 @@ void SimplifyMarkupCommand::doApply() // 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 = NodeTraversal::next(node)) { + for (Node* node = m_firstNode.get(); node && node != m_nodeAfterLast; node = NodeTraversal::next(*node)) { if (node->firstChild() || (node->isTextNode() && node->nextSibling())) continue; Node* startingNode = node->parentNode(); - RenderStyle* startingStyle = startingNode->renderStyle(); + auto* startingStyle = startingNode->renderStyle(); if (!startingStyle) continue; Node* currentNode = startingNode; - Node* topNodeWithStartingStyle = 0; + Node* topNodeWithStartingStyle = nullptr; while (currentNode != rootNode) { if (currentNode->parentNode() != rootNode && isRemovableBlock(currentNode)) nodesToRemove.append(currentNode); @@ -70,16 +70,17 @@ void SimplifyMarkupCommand::doApply() if (!currentNode) break; - if (!currentNode->renderer() || !currentNode->renderer()->isRenderInline() || toRenderInline(currentNode->renderer())->alwaysCreateLineBoxes()) + auto* renderer = currentNode->renderer(); + if (!is<RenderInline>(renderer) || downcast<RenderInline>(*renderer).alwaysCreateLineBoxes()) continue; if (currentNode->firstChild() != currentNode->lastChild()) { - topNodeWithStartingStyle = 0; + topNodeWithStartingStyle = nullptr; break; } unsigned context; - if (currentNode->renderStyle()->diff(startingStyle, context) == StyleDifferenceEqual) + if (currentNode->renderStyle()->diff(*startingStyle, context) == StyleDifferenceEqual) topNodeWithStartingStyle = currentNode; } diff --git a/Source/WebCore/editing/SimplifyMarkupCommand.h b/Source/WebCore/editing/SimplifyMarkupCommand.h index f1c5deea9..2d8257a45 100644 --- a/Source/WebCore/editing/SimplifyMarkupCommand.h +++ b/Source/WebCore/editing/SimplifyMarkupCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SimplifyMarkupCommand_h -#define SimplifyMarkupCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -32,15 +31,15 @@ namespace WebCore { class SimplifyMarkupCommand : public CompositeEditCommand { public: - static PassRefPtr<SimplifyMarkupCommand> create(Document& document, Node* firstNode, Node* nodeAfterLast) + static Ref<SimplifyMarkupCommand> create(Document& document, Node* firstNode, Node* nodeAfterLast) { - return adoptRef(new SimplifyMarkupCommand(document, firstNode, nodeAfterLast)); + return adoptRef(*new SimplifyMarkupCommand(document, firstNode, nodeAfterLast)); } private: SimplifyMarkupCommand(Document&, Node* firstNode, Node* nodeAfterLast); - virtual void doApply(); + void doApply() override; int pruneSubsequentAncestorsToRemove(Vector<RefPtr<Node>>& nodesToRemove, size_t startNodeIndex); RefPtr<Node> m_firstNode; @@ -48,5 +47,3 @@ private: }; } // namespace WebCore - -#endif // SimplifyMarkupCommand_h diff --git a/Source/WebCore/editing/SmartReplace.cpp b/Source/WebCore/editing/SmartReplace.cpp index 034dbfebb..0b4764cc0 100644 --- a/Source/WebCore/editing/SmartReplace.cpp +++ b/Source/WebCore/editing/SmartReplace.cpp @@ -11,7 +11,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -34,15 +34,14 @@ #include <unicode/uset.h> #include <wtf/Assertions.h> -#include <wtf/text/WTFString.h> +#include <wtf/text/StringView.h> namespace WebCore { static void addAllCodePoints(USet* smartSet, const String& string) { - const UChar* characters = string.characters(); for (size_t i = 0; i < string.length(); i++) - uset_add(smartSet, characters[i]); + uset_add(smartSet, string[i]); } // This is mostly a port of the code in WebCore/editing/SmartReplaceCF.cpp @@ -56,7 +55,7 @@ static USet* getSmartSet(bool isPreviousCharacter) // Whitespace and newline (kCFCharacterSetWhitespaceAndNewline) UErrorCode ec = U_ZERO_ERROR; String whitespaceAndNewline = ASCIILiteral("[[:WSpace:] [\\u000A\\u000B\\u000C\\u000D\\u0085]]"); - smartSet = uset_openPattern(whitespaceAndNewline.characters(), whitespaceAndNewline.length(), &ec); + smartSet = uset_openPattern(StringView(whitespaceAndNewline).upconvertedCharacters(), whitespaceAndNewline.length(), &ec); ASSERT(U_SUCCESS(ec)); // CJK ranges @@ -83,7 +82,7 @@ static USet* getSmartSet(bool isPreviousCharacter) // Punctuation (kCFCharacterSetPunctuation) UErrorCode ec = U_ZERO_ERROR; String punctuationClass = ASCIILiteral("[:P:]"); - USet* icuPunct = uset_openPattern(punctuationClass.characters(), punctuationClass.length(), &ec); + USet* icuPunct = uset_openPattern(StringView(punctuationClass).upconvertedCharacters(), punctuationClass.length(), &ec); ASSERT(U_SUCCESS(ec)); uset_addAll(smartSet, icuPunct); uset_close(icuPunct); diff --git a/Source/WebCore/editing/SmartReplace.h b/Source/WebCore/editing/SmartReplace.h index b072e58d7..dd2586753 100644 --- a/Source/WebCore/editing/SmartReplace.h +++ b/Source/WebCore/editing/SmartReplace.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,15 +26,12 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SmartReplace_h -#define SmartReplace_h +#pragma once -#include <wtf/unicode/Unicode.h> +#include <unicode/utypes.h> namespace WebCore { -bool isCharacterSmartReplaceExempt(UChar32 c, bool isPreviousCharacter); +WEBCORE_EXPORT bool isCharacterSmartReplaceExempt(UChar32 c, bool isPreviousCharacter); } // namespace WebCore - -#endif // SmartReplace_h diff --git a/Source/WebCore/editing/SmartReplaceCF.cpp b/Source/WebCore/editing/SmartReplaceCF.cpp new file mode 100644 index 000000000..85e9bc4aa --- /dev/null +++ b/Source/WebCore/editing/SmartReplaceCF.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2007 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. + * 3. Neither the name of Apple Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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 "SmartReplace.h" + +#include <CoreFoundation/CFCharacterSet.h> +#include <CoreFoundation/CFString.h> + +namespace WebCore { + +static CFMutableCharacterSetRef getSmartSet(bool isPreviousCharacter) +{ + static CFMutableCharacterSetRef preSmartSet; + static CFMutableCharacterSetRef postSmartSet; + CFMutableCharacterSetRef smartSet = isPreviousCharacter ? preSmartSet : postSmartSet; + if (!smartSet) { + smartSet = CFCharacterSetCreateMutable(kCFAllocatorDefault); + CFCharacterSetAddCharactersInString(smartSet, isPreviousCharacter ? CFSTR("([\"\'#$/-`{") : CFSTR(")].,;:?\'!\"%*-/}")); + CFCharacterSetUnion(smartSet, CFCharacterSetGetPredefined(kCFCharacterSetWhitespaceAndNewline)); + // Adding CJK ranges + CFCharacterSetAddCharactersInRange(smartSet, CFRangeMake(0x1100, 256)); // Hangul Jamo (0x1100 - 0x11FF) + CFCharacterSetAddCharactersInRange(smartSet, CFRangeMake(0x2E80, 352)); // CJK & Kangxi Radicals (0x2E80 - 0x2FDF) + CFCharacterSetAddCharactersInRange(smartSet, CFRangeMake(0x2FF0, 464)); // Ideograph Descriptions, CJK Symbols, Hiragana, Katakana, Bopomofo, Hangul Compatibility Jamo, Kanbun, & Bopomofo Ext (0x2FF0 - 0x31BF) + CFCharacterSetAddCharactersInRange(smartSet, CFRangeMake(0x3200, 29392)); // Enclosed CJK, CJK Ideographs (Uni Han & Ext A), & Yi (0x3200 - 0xA4CF) + CFCharacterSetAddCharactersInRange(smartSet, CFRangeMake(0xAC00, 11183)); // Hangul Syllables (0xAC00 - 0xD7AF) + CFCharacterSetAddCharactersInRange(smartSet, CFRangeMake(0xF900, 352)); // CJK Compatibility Ideographs (0xF900 - 0xFA5F) + CFCharacterSetAddCharactersInRange(smartSet, CFRangeMake(0xFE30, 32)); // CJK Compatibility From (0xFE30 - 0xFE4F) + CFCharacterSetAddCharactersInRange(smartSet, CFRangeMake(0xFF00, 240)); // Half/Full Width Form (0xFF00 - 0xFFEF) + CFCharacterSetAddCharactersInRange(smartSet, CFRangeMake(0x20000, 0xA6D7)); // CJK Ideograph Exntension B + CFCharacterSetAddCharactersInRange(smartSet, CFRangeMake(0x2F800, 0x021E)); // CJK Compatibility Ideographs (0x2F800 - 0x2FA1D) + + if (isPreviousCharacter) + preSmartSet = smartSet; + else { + CFCharacterSetUnion(smartSet, CFCharacterSetGetPredefined(kCFCharacterSetPunctuation)); + postSmartSet = smartSet; + } + } + return smartSet; +} + +bool isCharacterSmartReplaceExempt(UChar32 c, bool isPreviousCharacter) +{ + return CFCharacterSetIsLongCharacterMember(getSmartSet(isPreviousCharacter), c); +} + +} diff --git a/Source/WebCore/editing/SpellChecker.cpp b/Source/WebCore/editing/SpellChecker.cpp index d2e4b82ab..7cb6237a4 100644 --- a/Source/WebCore/editing/SpellChecker.cpp +++ b/Source/WebCore/editing/SpellChecker.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -30,9 +30,6 @@ #include "DocumentMarkerController.h" #include "Editor.h" #include "Frame.h" -#include "HTMLInputElement.h" -#include "HTMLTextAreaElement.h" -#include "Node.h" #include "Page.h" #include "PositionIterator.h" #include "RenderObject.h" @@ -44,10 +41,9 @@ namespace WebCore { SpellCheckRequest::SpellCheckRequest(PassRefPtr<Range> checkingRange, PassRefPtr<Range> paragraphRange, const String& text, TextCheckingTypeMask mask, TextCheckingProcessType processType) - : m_checker(0) - , m_checkingRange(checkingRange) + : m_checkingRange(checkingRange) , m_paragraphRange(paragraphRange) - , m_rootEditableElement(m_checkingRange->startContainer()->rootEditableElement()) + , m_rootEditableElement(m_checkingRange->startContainer().rootEditableElement()) , m_requestData(unrequestedTextCheckingSequence, text, mask, processType) { } @@ -57,16 +53,16 @@ SpellCheckRequest::~SpellCheckRequest() } // static -PassRefPtr<SpellCheckRequest> SpellCheckRequest::create(TextCheckingTypeMask textCheckingOptions, TextCheckingProcessType processType, PassRefPtr<Range> checkingRange, PassRefPtr<Range> paragraphRange) +RefPtr<SpellCheckRequest> SpellCheckRequest::create(TextCheckingTypeMask textCheckingOptions, TextCheckingProcessType processType, PassRefPtr<Range> checkingRange, PassRefPtr<Range> paragraphRange) { ASSERT(checkingRange); ASSERT(paragraphRange); String text = checkingRange->text(); if (!text.length()) - return PassRefPtr<SpellCheckRequest>(); + return nullptr; - return adoptRef(new SpellCheckRequest(checkingRange, paragraphRange, text, textCheckingOptions, processType)); + return adoptRef(*new SpellCheckRequest(checkingRange, paragraphRange, text, textCheckingOptions, processType)); } const TextCheckingRequestData& SpellCheckRequest::data() const @@ -78,16 +74,20 @@ void SpellCheckRequest::didSucceed(const Vector<TextCheckingResult>& results) { if (!m_checker) return; + + Ref<SpellCheckRequest> protectedThis(*this); m_checker->didCheckSucceed(m_requestData.sequence(), results); - m_checker = 0; + m_checker = nullptr; } void SpellCheckRequest::didCancel() { if (!m_checker) return; + + Ref<SpellCheckRequest> protectedThis(*this); m_checker->didCheckCancel(m_requestData.sequence()); - m_checker = 0; + m_checker = nullptr; } void SpellCheckRequest::setCheckerAndSequence(SpellChecker* requester, int sequence) @@ -100,14 +100,14 @@ void SpellCheckRequest::setCheckerAndSequence(SpellChecker* requester, int seque void SpellCheckRequest::requesterDestroyed() { - m_checker = 0; + m_checker = nullptr; } SpellChecker::SpellChecker(Frame& frame) : m_frame(frame) , m_lastRequestSequence(0) , m_lastProcessedSequence(0) - , m_timerToProcessQueuedRequest(this, &SpellChecker::timerFiredToProcessQueuedRequest) + , m_timerToProcessQueuedRequest(*this, &SpellChecker::timerFiredToProcessQueuedRequest) { } @@ -115,19 +115,19 @@ SpellChecker::~SpellChecker() { if (m_processingRequest) m_processingRequest->requesterDestroyed(); - for (RequestQueue::iterator i = m_requestQueue.begin(); i != m_requestQueue.end(); ++i) - (*i)->requesterDestroyed(); + for (auto& queue : m_requestQueue) + queue->requesterDestroyed(); } TextCheckerClient* SpellChecker::client() const { Page* page = m_frame.page(); if (!page) - return 0; - return page->editorClient()->textChecker(); + return nullptr; + return page->editorClient().textChecker(); } -void SpellChecker::timerFiredToProcessQueuedRequest(Timer<SpellChecker>*) +void SpellChecker::timerFiredToProcessQueuedRequest() { ASSERT(!m_requestQueue.isEmpty()); if (m_requestQueue.isEmpty()) @@ -150,8 +150,8 @@ bool SpellChecker::isCheckable(Range* range) const { if (!range || !range->firstNode() || !range->firstNode()->renderer()) return false; - const Node* node = range->startContainer(); - if (node && node->isElementNode() && !toElement(node)->isSpellCheckingEnabled()) + const Node& node = range->startContainer(); + if (is<Element>(node) && !downcast<Element>(node).isSpellCheckingEnabled()) return false; return true; } @@ -182,18 +182,18 @@ void SpellChecker::invokeRequest(PassRefPtr<SpellCheckRequest> request) if (!client()) return; m_processingRequest = request; - client()->requestCheckingOfString(m_processingRequest); + client()->requestCheckingOfString(*m_processingRequest, m_frame.selection().selection()); } void SpellChecker::enqueueRequest(PassRefPtr<SpellCheckRequest> request) { ASSERT(request); - for (RequestQueue::iterator it = m_requestQueue.begin(); it != m_requestQueue.end(); ++it) { - if (request->rootEditableElement() != (*it)->rootEditableElement()) + for (auto& queue : m_requestQueue) { + if (request->rootEditableElement() != queue->rootEditableElement()) continue; - *it = request; + queue = request; return; } @@ -214,7 +214,7 @@ void SpellChecker::didCheck(int sequence, const Vector<TextCheckingResult>& resu if (m_lastProcessedSequence < sequence) m_lastProcessedSequence = sequence; - m_processingRequest.clear(); + m_processingRequest = nullptr; if (!m_requestQueue.isEmpty()) m_timerToProcessQueuedRequest.startOneShot(0); } @@ -236,8 +236,7 @@ void SpellChecker::didCheckSucceed(int sequence, const Vector<TextCheckingResult void SpellChecker::didCheckCancel(int sequence) { - Vector<TextCheckingResult> results; - didCheck(sequence, results); + didCheck(sequence, Vector<TextCheckingResult>()); } } // namespace WebCore diff --git a/Source/WebCore/editing/SpellChecker.h b/Source/WebCore/editing/SpellChecker.h index a0c3a247d..049d1c51f 100644 --- a/Source/WebCore/editing/SpellChecker.h +++ b/Source/WebCore/editing/SpellChecker.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SpellChecker_h -#define SpellChecker_h +#pragma once #include "Element.h" #include "Range.h" @@ -45,7 +44,7 @@ class SpellChecker; class SpellCheckRequest : public TextCheckingRequest { public: - static PassRefPtr<SpellCheckRequest> create(TextCheckingTypeMask, TextCheckingProcessType, PassRefPtr<Range> checkingRange, PassRefPtr<Range> paragraphRange); + static RefPtr<SpellCheckRequest> create(TextCheckingTypeMask, TextCheckingProcessType, PassRefPtr<Range> checkingRange, PassRefPtr<Range> paragraphRange); virtual ~SpellCheckRequest(); PassRefPtr<Range> checkingRange() const { return m_checkingRange; } @@ -56,14 +55,14 @@ public: void requesterDestroyed(); bool isStarted() const { return m_checker; } - virtual const TextCheckingRequestData& data() const override; - virtual void didSucceed(const Vector<TextCheckingResult>&) override; - virtual void didCancel() override; + const TextCheckingRequestData& data() const override; + void didSucceed(const Vector<TextCheckingResult>&) override; + void didCancel() override; private: SpellCheckRequest(PassRefPtr<Range> checkingRange, PassRefPtr<Range> paragraphRange, const String&, TextCheckingTypeMask, TextCheckingProcessType); - SpellChecker* m_checker; + SpellChecker* m_checker { nullptr }; RefPtr<Range> m_checkingRange; RefPtr<Range> m_paragraphRange; RefPtr<Element> m_rootEditableElement; @@ -98,7 +97,7 @@ private: bool canCheckAsynchronously(Range*) const; TextCheckerClient* client() const; - void timerFiredToProcessQueuedRequest(Timer<SpellChecker>*); + void timerFiredToProcessQueuedRequest(); void invokeRequest(PassRefPtr<SpellCheckRequest>); void enqueueRequest(PassRefPtr<SpellCheckRequest>); void didCheckSucceed(int sequence, const Vector<TextCheckingResult>&); @@ -109,12 +108,10 @@ private: int m_lastRequestSequence; int m_lastProcessedSequence; - Timer<SpellChecker> m_timerToProcessQueuedRequest; + Timer m_timerToProcessQueuedRequest; RefPtr<SpellCheckRequest> m_processingRequest; RequestQueue m_requestQueue; }; } // namespace WebCore - -#endif // SpellChecker_h diff --git a/Source/WebCore/editing/SpellingCorrectionCommand.cpp b/Source/WebCore/editing/SpellingCorrectionCommand.cpp index c0ba961bd..c9bf2c099 100644 --- a/Source/WebCore/editing/SpellingCorrectionCommand.cpp +++ b/Source/WebCore/editing/SpellingCorrectionCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 Apple Inc. All rights reserved. + * Copyright (C) 2011, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -27,12 +27,15 @@ #include "SpellingCorrectionCommand.h" #include "AlternativeTextController.h" +#include "DataTransfer.h" #include "Document.h" #include "DocumentFragment.h" #include "Editor.h" #include "Frame.h" #include "ReplaceSelectionCommand.h" #include "SetSelectionCommand.h" +#include "StaticRange.h" +#include "TextIterator.h" #include "markup.h" namespace WebCore { @@ -42,9 +45,9 @@ namespace WebCore { // This information is needed by spell checking service to update user specific data. class SpellingCorrectionRecordUndoCommand : public SimpleEditCommand { public: - static PassRefPtr<SpellingCorrectionRecordUndoCommand> create(Document& document, const String& corrected, const String& correction) + static Ref<SpellingCorrectionRecordUndoCommand> create(Document& document, const String& corrected, const String& correction) { - return adoptRef(new SpellingCorrectionRecordUndoCommand(document, corrected, correction)); + return adoptRef(*new SpellingCorrectionRecordUndoCommand(document, corrected, correction)); } private: SpellingCorrectionRecordUndoCommand(Document& document, const String& corrected, const String& correction) @@ -55,11 +58,11 @@ private: { } - virtual void doApply() override + void doApply() override { } - virtual void doUnapply() override + void doUnapply() override { if (!m_hasBeenUndone) { frame().editor().unappliedSpellCorrection(startingSelection(), m_corrected, m_correction); @@ -69,7 +72,7 @@ private: } #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override + void getNodesInCommand(HashSet<Node*>&) override { } #endif @@ -81,13 +84,19 @@ private: #endif SpellingCorrectionCommand::SpellingCorrectionCommand(PassRefPtr<Range> rangeToBeCorrected, const String& correction) - : CompositeEditCommand(rangeToBeCorrected->startContainer()->document()) + : CompositeEditCommand(rangeToBeCorrected->startContainer().document(), EditActionInsertReplacement) , m_rangeToBeCorrected(rangeToBeCorrected) - , m_selectionToBeCorrected(m_rangeToBeCorrected.get()) + , m_selectionToBeCorrected(*m_rangeToBeCorrected) , m_correction(correction) { } +bool SpellingCorrectionCommand::willApplyCommand() +{ + m_correctionFragment = createFragmentFromText(*m_rangeToBeCorrected, m_correction); + return CompositeEditCommand::willApplyCommand(); +} + void SpellingCorrectionCommand::doApply() { m_corrected = plainText(m_rangeToBeCorrected.get()); @@ -100,16 +109,34 @@ void SpellingCorrectionCommand::doApply() if (!m_rangeToBeCorrected) return; - RefPtr<DocumentFragment> fragment = createFragmentFromText(*m_rangeToBeCorrected, m_correction); - if (!fragment) - return; - - applyCommandToComposite(SetSelectionCommand::create(m_selectionToBeCorrected, FrameSelection::SpellCorrectionTriggered | FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle)); + applyCommandToComposite(SetSelectionCommand::create(m_selectionToBeCorrected, FrameSelection::defaultSetSelectionOptions() | FrameSelection::SpellCorrectionTriggered)); #if USE(AUTOCORRECTION_PANEL) applyCommandToComposite(SpellingCorrectionRecordUndoCommand::create(document(), m_corrected, m_correction)); #endif - applyCommandToComposite(ReplaceSelectionCommand::create(document(), fragment.release(), ReplaceSelectionCommand::MatchStyle, EditActionPaste)); + applyCommandToComposite(ReplaceSelectionCommand::create(document(), WTFMove(m_correctionFragment), ReplaceSelectionCommand::MatchStyle, EditActionPaste)); +} + +String SpellingCorrectionCommand::inputEventData() const +{ + if (isEditingTextAreaOrTextInput()) + return m_correction; + + return CompositeEditCommand::inputEventData(); +} + +Vector<RefPtr<StaticRange>> SpellingCorrectionCommand::targetRanges() const +{ + RefPtr<StaticRange> range = StaticRange::createFromRange(*m_rangeToBeCorrected); + return { 1, range }; +} + +RefPtr<DataTransfer> SpellingCorrectionCommand::inputEventDataTransfer() const +{ + if (!isEditingTextAreaOrTextInput()) + return DataTransfer::createForInputEvent(m_correction, createMarkup(*m_correctionFragment)); + + return CompositeEditCommand::inputEventDataTransfer(); } bool SpellingCorrectionCommand::shouldRetainAutocorrectionIndicator() const diff --git a/Source/WebCore/editing/SpellingCorrectionCommand.h b/Source/WebCore/editing/SpellingCorrectionCommand.h index 5d170f47b..6c439a7a5 100644 --- a/Source/WebCore/editing/SpellingCorrectionCommand.h +++ b/Source/WebCore/editing/SpellingCorrectionCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SpellingCorrectionCommand_h -#define SpellingCorrectionCommand_h +#pragma once #include "CompositeEditCommand.h" #include "Range.h" @@ -33,21 +32,25 @@ namespace WebCore { class SpellingCorrectionCommand : public CompositeEditCommand { public: - static PassRefPtr<SpellingCorrectionCommand> create(PassRefPtr<Range> rangeToBeCorrected, const String& correction) + static Ref<SpellingCorrectionCommand> create(PassRefPtr<Range> rangeToBeCorrected, const String& correction) { - return adoptRef(new SpellingCorrectionCommand(rangeToBeCorrected, correction)); + return adoptRef(*new SpellingCorrectionCommand(rangeToBeCorrected, correction)); } private: SpellingCorrectionCommand(PassRefPtr<Range> rangeToBeCorrected, const String& correction); - virtual void doApply() override; - virtual bool shouldRetainAutocorrectionIndicator() const override; + bool willApplyCommand() final; + void doApply() override; + bool shouldRetainAutocorrectionIndicator() const override; + + String inputEventData() const final; + Vector<RefPtr<StaticRange>> targetRanges() const final; + RefPtr<DataTransfer> inputEventDataTransfer() const final; RefPtr<Range> m_rangeToBeCorrected; VisibleSelection m_selectionToBeCorrected; + RefPtr<DocumentFragment> m_correctionFragment; String m_corrected; String m_correction; }; } // namespace WebCore - -#endif // SpellingCorrectionCommand_h diff --git a/Source/WebCore/editing/SplitElementCommand.cpp b/Source/WebCore/editing/SplitElementCommand.cpp index 6ad8f1921..2d4e85498 100644 --- a/Source/WebCore/editing/SplitElementCommand.cpp +++ b/Source/WebCore/editing/SplitElementCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -47,30 +47,26 @@ void SplitElementCommand::executeApply() if (m_atChild->parentNode() != m_element2) return; - Vector<RefPtr<Node>> children; + Vector<Ref<Node>> children; for (Node* node = m_element2->firstChild(); node != m_atChild; node = node->nextSibling()) - children.append(node); - - ExceptionCode ec = 0; - - ContainerNode* parent = m_element2->parentNode(); + children.append(*node); + + auto* parent = m_element2->parentNode(); if (!parent || !parent->hasEditableStyle()) return; - parent->insertBefore(m_element1.get(), m_element2.get(), ec); - if (ec) + if (parent->insertBefore(*m_element1, m_element2.get()).hasException()) return; // Delete id attribute from the second element because the same id cannot be used for more than one element m_element2->removeAttribute(HTMLNames::idAttr); - size_t size = children.size(); - for (size_t i = 0; i < size; ++i) - m_element1->appendChild(children[i], ec); + for (auto& child : children) + m_element1->appendChild(child); } void SplitElementCommand::doApply() { - m_element1 = m_element2->cloneElementWithoutChildren(); + m_element1 = m_element2->cloneElementWithoutChildren(document()); executeApply(); } @@ -80,21 +76,21 @@ void SplitElementCommand::doUnapply() if (!m_element1 || !m_element1->hasEditableStyle() || !m_element2->hasEditableStyle()) return; - Vector<RefPtr<Node>> children; + Vector<Ref<Node>> children; for (Node* node = m_element1->firstChild(); node; node = node->nextSibling()) - children.append(node); + children.append(*node); RefPtr<Node> refChild = m_element2->firstChild(); - size_t size = children.size(); - for (size_t i = 0; i < size; ++i) - m_element2->insertBefore(children[i].get(), refChild.get(), IGNORE_EXCEPTION); + for (auto& child : children) + m_element2->insertBefore(child, refChild.get()); // Recover the id attribute of the original element. - if (m_element1->hasAttribute(HTMLNames::idAttr)) - m_element2->setAttribute(HTMLNames::idAttr, m_element1->getAttribute(HTMLNames::idAttr)); + const AtomicString& id = m_element1->getIdAttribute(); + if (!id.isNull()) + m_element2->setIdAttribute(id); - m_element1->remove(IGNORE_EXCEPTION); + m_element1->remove(); } void SplitElementCommand::doReapply() diff --git a/Source/WebCore/editing/SplitElementCommand.h b/Source/WebCore/editing/SplitElementCommand.h index 4f863fee2..ca1d0b2bc 100644 --- a/Source/WebCore/editing/SplitElementCommand.h +++ b/Source/WebCore/editing/SplitElementCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SplitElementCommand_h -#define SplitElementCommand_h +#pragma once #include "EditCommand.h" @@ -32,21 +31,21 @@ namespace WebCore { class SplitElementCommand : public SimpleEditCommand { public: - static PassRefPtr<SplitElementCommand> create(PassRefPtr<Element> element, PassRefPtr<Node> splitPointChild) + static Ref<SplitElementCommand> create(PassRefPtr<Element> element, PassRefPtr<Node> splitPointChild) { - return adoptRef(new SplitElementCommand(element, splitPointChild)); + return adoptRef(*new SplitElementCommand(element, splitPointChild)); } private: SplitElementCommand(PassRefPtr<Element>, PassRefPtr<Node> splitPointChild); - virtual void doApply() override; - virtual void doUnapply() override; - virtual void doReapply() override; + void doApply() override; + void doUnapply() override; + void doReapply() override; void executeApply(); #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override; + void getNodesInCommand(HashSet<Node*>&) override; #endif RefPtr<Element> m_element1; @@ -55,5 +54,3 @@ private: }; } // namespace WebCore - -#endif // SplitElementCommand_h diff --git a/Source/WebCore/editing/SplitTextNodeCommand.cpp b/Source/WebCore/editing/SplitTextNodeCommand.cpp index e7108f514..b0a4b21f9 100644 --- a/Source/WebCore/editing/SplitTextNodeCommand.cpp +++ b/Source/WebCore/editing/SplitTextNodeCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -54,11 +54,14 @@ void SplitTextNodeCommand::doApply() if (!parent || !parent->hasEditableStyle()) return; - String prefixText = m_text2->substringData(0, m_offset, IGNORE_EXCEPTION); + auto result = m_text2->substringData(0, m_offset); + if (result.hasException()) + return; + auto prefixText = result.releaseReturnValue(); if (prefixText.isEmpty()) return; - m_text1 = Text::create(document(), prefixText); + m_text1 = Text::create(document(), WTFMove(prefixText)); ASSERT(m_text1); document().markers().copyMarkers(m_text2.get(), 0, m_offset, m_text1.get(), 0); @@ -74,10 +77,10 @@ void SplitTextNodeCommand::doUnapply() String prefixText = m_text1->data(); - m_text2->insertData(0, prefixText, ASSERT_NO_EXCEPTION); + m_text2->insertData(0, prefixText); document().markers().copyMarkers(m_text1.get(), 0, prefixText.length(), m_text2.get(), 0); - m_text1->remove(ASSERT_NO_EXCEPTION); + m_text1->remove(); } void SplitTextNodeCommand::doReapply() @@ -94,19 +97,19 @@ void SplitTextNodeCommand::doReapply() void SplitTextNodeCommand::insertText1AndTrimText2() { - ExceptionCode ec = 0; - m_text2->parentNode()->insertBefore(m_text1.get(), m_text2.get(), ec); - if (ec) + if (m_text2->parentNode()->insertBefore(*m_text1, m_text2.get()).hasException()) return; - m_text2->deleteData(0, m_offset, ec); + m_text2->deleteData(0, m_offset); } #ifndef NDEBUG + void SplitTextNodeCommand::getNodesInCommand(HashSet<Node*>& nodes) { addNodeAndDescendants(m_text1.get(), nodes); addNodeAndDescendants(m_text2.get(), nodes); } + #endif } // namespace WebCore diff --git a/Source/WebCore/editing/SplitTextNodeCommand.h b/Source/WebCore/editing/SplitTextNodeCommand.h index 282945551..567479e15 100644 --- a/Source/WebCore/editing/SplitTextNodeCommand.h +++ b/Source/WebCore/editing/SplitTextNodeCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SplitTextNodeCommand_h -#define SplitTextNodeCommand_h +#pragma once #include "EditCommand.h" @@ -34,21 +33,21 @@ class Text; class SplitTextNodeCommand : public SimpleEditCommand { public: - static PassRefPtr<SplitTextNodeCommand> create(PassRefPtr<Text> node, int offset) + static Ref<SplitTextNodeCommand> create(PassRefPtr<Text> node, int offset) { - return adoptRef(new SplitTextNodeCommand(node, offset)); + return adoptRef(*new SplitTextNodeCommand(node, offset)); } private: SplitTextNodeCommand(PassRefPtr<Text>, int offset); - virtual void doApply() override; - virtual void doUnapply() override; - virtual void doReapply() override; + void doApply() override; + void doUnapply() override; + void doReapply() override; void insertText1AndTrimText2(); #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override; + void getNodesInCommand(HashSet<Node*>&) override; #endif RefPtr<Text> m_text1; @@ -57,5 +56,3 @@ private: }; } // namespace WebCore - -#endif // SplitTextNodeCommand_h diff --git a/Source/WebCore/editing/SplitTextNodeContainingElementCommand.cpp b/Source/WebCore/editing/SplitTextNodeContainingElementCommand.cpp index 33ab429cc..ce8887469 100644 --- a/Source/WebCore/editing/SplitTextNodeContainingElementCommand.cpp +++ b/Source/WebCore/editing/SplitTextNodeContainingElementCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -55,11 +55,11 @@ void SplitTextNodeContainingElementCommand::doApply() RenderElement* parentRenderer = parent->renderer(); if (!parentRenderer || !parentRenderer->isInline()) { - wrapContentsInDummySpan(parent); + wrapContentsInDummySpan(*parent); Node* firstChild = parent->firstChild(); - if (!firstChild || !firstChild->isElementNode()) + if (!is<Element>(firstChild)) return; - parent = toElement(firstChild); + parent = downcast<Element>(firstChild); } splitElement(parent, m_text); diff --git a/Source/WebCore/editing/SplitTextNodeContainingElementCommand.h b/Source/WebCore/editing/SplitTextNodeContainingElementCommand.h index 4e6af4f73..5e69e12f0 100644 --- a/Source/WebCore/editing/SplitTextNodeContainingElementCommand.h +++ b/Source/WebCore/editing/SplitTextNodeContainingElementCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SplitTextNodeContainingElementCommand_h -#define SplitTextNodeContainingElementCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -32,20 +31,18 @@ namespace WebCore { class SplitTextNodeContainingElementCommand : public CompositeEditCommand { public: - static PassRefPtr<SplitTextNodeContainingElementCommand> create(PassRefPtr<Text> node, int offset) + static Ref<SplitTextNodeContainingElementCommand> create(PassRefPtr<Text> node, int offset) { - return adoptRef(new SplitTextNodeContainingElementCommand(node, offset)); + return adoptRef(*new SplitTextNodeContainingElementCommand(node, offset)); } private: SplitTextNodeContainingElementCommand(PassRefPtr<Text>, int offset); - virtual void doApply(); + void doApply() override; RefPtr<Text> m_text; int m_offset; }; } // namespace WebCore - -#endif // SplitTextNodeContainingElementCommand_h diff --git a/Source/WebCore/editing/SurroundingText.h b/Source/WebCore/editing/SurroundingText.h new file mode 100644 index 000000000..bd6e36650 --- /dev/null +++ b/Source/WebCore/editing/SurroundingText.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#pragma once + +#include <wtf/text/WTFString.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 diff --git a/Source/WebCore/editing/TextAffinity.h b/Source/WebCore/editing/TextAffinity.h index 90e665d3b..940edca45 100644 --- a/Source/WebCore/editing/TextAffinity.h +++ b/Source/WebCore/editing/TextAffinity.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2004-2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,39 +23,10 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TextAffinity_h -#define TextAffinity_h - -#ifdef __OBJC__ -#if !PLATFORM(IOS) -#include <AppKit/NSTextView.h> -#else -#include "WAKAppKitStubs.h" -#endif // !PLATFORM(IOS) -#endif +#pragma once namespace WebCore { -// These match the AppKit values for these concepts. -// From NSTextView.h: -// NSSelectionAffinityUpstream = 0 -// NSSelectionAffinityDownstream = 1 enum EAffinity { UPSTREAM = 0, DOWNSTREAM = 1 }; } // namespace WebCore - -#ifdef __OBJC__ - -inline NSSelectionAffinity kit(WebCore::EAffinity affinity) -{ - return static_cast<NSSelectionAffinity>(affinity); -} - -inline WebCore::EAffinity core(NSSelectionAffinity affinity) -{ - return static_cast<WebCore::EAffinity>(affinity); -} - -#endif - -#endif // TextAffinity_h diff --git a/Source/WebCore/editing/TextCheckingHelper.cpp b/Source/WebCore/editing/TextCheckingHelper.cpp index 0e89cfccc..379d7d299 100644 --- a/Source/WebCore/editing/TextCheckingHelper.cpp +++ b/Source/WebCore/editing/TextCheckingHelper.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2006, 2007, 2014 Apple Inc. All rights reserved. * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) * * Redistribution and use in source and binary forms, with or without @@ -11,10 +11,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -29,82 +29,92 @@ #include "Document.h" #include "DocumentMarkerController.h" +#include "ExceptionCode.h" #include "Frame.h" +#include "FrameSelection.h" #include "Settings.h" -#include "TextBreakIterator.h" #include "TextCheckerClient.h" #include "TextIterator.h" #include "VisiblePosition.h" #include "VisibleUnits.h" +#include <unicode/ubrk.h> #include <wtf/text/StringView.h> +#include <wtf/text/TextBreakIterator.h> namespace WebCore { #if !USE(UNIFIED_TEXT_CHECKING) #if USE(GRAMMAR_CHECKING) -static void findBadGrammars(TextCheckerClient* client, const UChar* text, int start, int length, Vector<TextCheckingResult>& results) -{ - int checkLocation = start; - int checkLength = length; - while (0 < checkLength) { +static void findGrammaticalErrors(TextCheckerClient& client, StringView text, Vector<TextCheckingResult>& results) +{ + for (unsigned checkLocation = 0; checkLocation < text.length(); ) { int badGrammarLocation = -1; int badGrammarLength = 0; Vector<GrammarDetail> badGrammarDetails; - client->checkGrammarOfString(text + checkLocation, checkLength, badGrammarDetails, &badGrammarLocation, &badGrammarLength); + client.checkGrammarOfString(text.substring(checkLocation), badGrammarDetails, &badGrammarLocation, &badGrammarLength); if (!badGrammarLength) break; - ASSERT(0 <= badGrammarLocation && badGrammarLocation <= checkLength); - ASSERT(0 < badGrammarLength && badGrammarLocation + badGrammarLength <= checkLength); + + ASSERT(badGrammarLocation >= 0); + ASSERT(static_cast<unsigned>(badGrammarLocation) <= text.length() - checkLocation); + ASSERT(badGrammarLength > 0); + ASSERT(static_cast<unsigned>(badGrammarLength) <= text.length() - checkLocation - badGrammarLocation); + TextCheckingResult badGrammar; badGrammar.type = TextCheckingTypeGrammar; badGrammar.location = checkLocation + badGrammarLocation; badGrammar.length = badGrammarLength; - badGrammar.details.swap(badGrammarDetails); + badGrammar.details = WTFMove(badGrammarDetails); results.append(badGrammar); - checkLocation += (badGrammarLocation + badGrammarLength); - checkLength -= (badGrammarLocation + badGrammarLength); + checkLocation += badGrammarLocation + badGrammarLength; } } + #endif -static void findMisspellings(TextCheckerClient* client, const UChar* text, int length, Vector<TextCheckingResult>& results) +static void findMisspellings(TextCheckerClient& client, StringView text, Vector<TextCheckingResult>& results) { - TextBreakIterator* iterator = wordBreakIterator(StringView(text, length)); + UBreakIterator* iterator = wordBreakIterator(text); if (!iterator) return; - int wordStart = textBreakCurrent(iterator); - while (0 <= wordStart) { - int wordEnd = textBreakNext(iterator); + for (int wordStart = ubrk_current(iterator); wordStart >= 0; ) { + int wordEnd = ubrk_next(iterator); if (wordEnd < 0) break; + int wordLength = wordEnd - wordStart; int misspellingLocation = -1; int misspellingLength = 0; - client->checkSpellingOfString(text + wordStart, wordLength, &misspellingLocation, &misspellingLength); - if (0 < misspellingLength) { - ASSERT(0 <= misspellingLocation && misspellingLocation <= wordLength); - ASSERT(0 < misspellingLength && misspellingLocation + misspellingLength <= wordLength); + client.checkSpellingOfString(text.substring(wordStart, wordLength), &misspellingLocation, &misspellingLength); + + if (misspellingLength > 0) { + ASSERT(misspellingLocation >= 0); + ASSERT(misspellingLocation <= wordLength); + ASSERT(misspellingLength > 0); + ASSERT(misspellingLocation + misspellingLength <= wordLength); + TextCheckingResult misspelling; misspelling.type = TextCheckingTypeSpelling; misspelling.location = wordStart + misspellingLocation; misspelling.length = misspellingLength; - misspelling.replacement = client->getAutoCorrectSuggestionForMisspelledWord(String(text + misspelling.location, misspelling.length)); + misspelling.replacement = client.getAutoCorrectSuggestionForMisspelledWord(text.substring(misspelling.location, misspelling.length).toStringWithoutCopying()); results.append(misspelling); } wordStart = wordEnd; } } + #endif -static PassRefPtr<Range> expandToParagraphBoundary(PassRefPtr<Range> range) +static Ref<Range> expandToParagraphBoundary(PassRefPtr<Range> range) { - RefPtr<Range> paragraphRange = range->cloneRange(IGNORE_EXCEPTION); - setStart(paragraphRange.get(), startOfParagraph(range->startPosition())); - setEnd(paragraphRange.get(), endOfParagraph(range->endPosition())); + Ref<Range> paragraphRange = range->cloneRange(); + setStart(paragraphRange.ptr(), startOfParagraph(range->startPosition())); + setEnd(paragraphRange.ptr(), endOfParagraph(range->endPosition())); return paragraphRange; } @@ -139,7 +149,7 @@ void TextCheckingParagraph::expandRangeToNextEnd() void TextCheckingParagraph::invalidateParagraphRangeValues() { m_checkingStart = m_checkingEnd = -1; - m_offsetAsRange = 0; + m_offsetAsRange = nullptr; m_text = String(); } @@ -153,7 +163,7 @@ PassRefPtr<Range> TextCheckingParagraph::paragraphRange() const { ASSERT(m_checkingRange); if (!m_paragraphRange) - m_paragraphRange = expandToParagraphBoundary(checkingRange()); + m_paragraphRange = expandToParagraphBoundary(m_checkingRange); return m_paragraphRange; } @@ -163,28 +173,31 @@ PassRefPtr<Range> TextCheckingParagraph::subrange(int characterOffset, int chara return TextIterator::subrange(paragraphRange().get(), characterOffset, characterCount); } -int TextCheckingParagraph::offsetTo(const Position& position, ExceptionCode& ec) const +ExceptionOr<int> TextCheckingParagraph::offsetTo(const Position& position) const { ASSERT(m_checkingRange); - RefPtr<Range> range = offsetAsRange()->cloneRange(ASSERT_NO_EXCEPTION); - range->setEnd(position.containerNode(), position.computeOffsetInContainerNode(), ec); - if (ec) - return 0; - return TextIterator::rangeLength(range.get()); + if (!position.containerNode()) + return Exception { TypeError }; + + auto range = offsetAsRange()->cloneRange(); + auto result = range->setEnd(*position.containerNode(), position.computeOffsetInContainerNode()); + if (result.hasException()) + return result.releaseException(); + return TextIterator::rangeLength(range.ptr()); } bool TextCheckingParagraph::isEmpty() const { // Both predicates should have same result, but we check both just for sure. // We need to investigate to remove this redundancy. - return isRangeEmpty() || isTextEmpty(); + return checkingStart() >= checkingEnd() || text().isEmpty(); } PassRefPtr<Range> TextCheckingParagraph::offsetAsRange() const { ASSERT(m_checkingRange); if (!m_offsetAsRange) - m_offsetAsRange = Range::create(paragraphRange()->startContainer()->document(), paragraphRange()->startPosition(), checkingRange()->startPosition()); + m_offsetAsRange = Range::create(paragraphRange()->startContainer().document(), paragraphRange()->startPosition(), m_checkingRange->startPosition()); return m_offsetAsRange; } @@ -209,7 +222,7 @@ int TextCheckingParagraph::checkingEnd() const { ASSERT(m_checkingRange); if (m_checkingEnd == -1) - m_checkingEnd = checkingStart() + TextIterator::rangeLength(checkingRange().get()); + m_checkingEnd = checkingStart() + TextIterator::rangeLength(m_checkingRange.get()); return m_checkingEnd; } @@ -217,7 +230,7 @@ int TextCheckingParagraph::checkingLength() const { ASSERT(m_checkingRange); if (-1 == m_checkingLength) - m_checkingLength = TextIterator::rangeLength(checkingRange().get()); + m_checkingLength = TextIterator::rangeLength(m_checkingRange.get()); return m_checkingLength; } @@ -233,68 +246,62 @@ TextCheckingHelper::~TextCheckingHelper() { } -#if !PLATFORM(IOS) String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange) { - WordAwareIterator it(m_range.get()); firstMisspellingOffset = 0; - + String firstMisspelling; int currentChunkOffset = 0; - while (!it.atEnd()) { - const UChar* chars = it.characters(); - int len = it.length(); - - // Skip some work for one-space-char hunks - if (!(len == 1 && chars[0] == ' ')) { - - int misspellingLocation = -1; - int misspellingLength = 0; - m_client->textChecker()->checkSpellingOfString(chars, len, &misspellingLocation, &misspellingLength); - - // 5490627 shows that there was some code path here where the String constructor below crashes. - // We don't know exactly what combination of bad input caused this, so we're making this much - // more robust against bad input on release builds. - ASSERT(misspellingLength >= 0); - ASSERT(misspellingLocation >= -1); - ASSERT(!misspellingLength || misspellingLocation >= 0); - ASSERT(misspellingLocation < len); - ASSERT(misspellingLength <= len); - ASSERT(misspellingLocation + misspellingLength <= len); - - if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) { - - // Compute range of misspelled word - RefPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength); - - // Remember first-encountered misspelling and its offset. - if (!firstMisspelling) { - firstMisspellingOffset = currentChunkOffset + misspellingLocation; - firstMisspelling = String(chars + misspellingLocation, misspellingLength); - firstMisspellingRange = misspellingRange; - } + for (WordAwareIterator it(*m_range); !it.atEnd(); currentChunkOffset += it.text().length(), it.advance()) { + StringView text = it.text(); + int textLength = text.length(); - // Store marker for misspelled word. - misspellingRange->startContainer()->document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling); + // Skip some work for one-space-char hunks. + if (textLength == 1 && text[0] == ' ') + continue; - // Bail out if we're marking only the first misspelling, and not all instances. - if (!markAll) - break; + int misspellingLocation = -1; + int misspellingLength = 0; + m_client->textChecker()->checkSpellingOfString(text, &misspellingLocation, &misspellingLength); + + // 5490627 shows that there was some code path here where the String constructor below crashes. + // We don't know exactly what combination of bad input caused this, so we're making this much + // more robust against bad input on release builds. + ASSERT(misspellingLength >= 0); + ASSERT(misspellingLocation >= -1); + ASSERT(!misspellingLength || misspellingLocation >= 0); + ASSERT(misspellingLocation < textLength); + ASSERT(misspellingLength <= textLength); + ASSERT(misspellingLocation + misspellingLength <= textLength); + + if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < textLength && misspellingLength <= textLength && misspellingLocation + misspellingLength <= textLength) { + // Compute range of misspelled word + RefPtr<Range> misspellingRange = TextIterator::subrange(m_range.get(), currentChunkOffset + misspellingLocation, misspellingLength); + + // Remember first-encountered misspelling and its offset. + if (!firstMisspelling) { + firstMisspellingOffset = currentChunkOffset + misspellingLocation; + firstMisspelling = text.substring(misspellingLocation, misspellingLength).toString(); + firstMisspellingRange = misspellingRange; } + + // Store marker for misspelled word. + misspellingRange->startContainer().document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling); + + // Bail out if we're marking only the first misspelling, and not all instances. + if (!markAll) + break; } - - currentChunkOffset += len; - it.advance(); } - + return firstMisspelling; } String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) { if (!unifiedTextCheckerEnabled()) - return ""; + return emptyString(); String firstFoundItem; String misspelledWord; @@ -306,36 +313,36 @@ String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, b outGrammarDetail.location = -1; outGrammarDetail.length = 0; outGrammarDetail.guesses.clear(); - outGrammarDetail.userDescription = ""; + outGrammarDetail.userDescription = emptyString(); // Expand the search range to encompass entire paragraphs, since text checking needs that much context. // Determine the character offset from the start of the paragraph to the start of the original search range, // since we will want to ignore results in this area. - RefPtr<Range> paragraphRange = m_range->cloneRange(IGNORE_EXCEPTION); - setStart(paragraphRange.get(), startOfParagraph(m_range->startPosition())); - int totalRangeLength = TextIterator::rangeLength(paragraphRange.get()); - setEnd(paragraphRange.get(), endOfParagraph(m_range->startPosition())); + Ref<Range> paragraphRange = m_range->cloneRange(); + setStart(paragraphRange.ptr(), startOfParagraph(m_range->startPosition())); + int totalRangeLength = TextIterator::rangeLength(paragraphRange.ptr()); + setEnd(paragraphRange.ptr(), endOfParagraph(m_range->startPosition())); - RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->startPosition()); - int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.get()); + Ref<Range> offsetAsRange = Range::create(paragraphRange->startContainer().document(), paragraphRange->startPosition(), m_range->startPosition()); + int rangeStartOffset = TextIterator::rangeLength(offsetAsRange.ptr()); int totalLengthProcessed = 0; bool firstIteration = true; bool lastIteration = false; while (totalLengthProcessed < totalRangeLength) { // Iterate through the search range by paragraphs, checking each one for spelling and grammar. - int currentLength = TextIterator::rangeLength(paragraphRange.get()); + int currentLength = TextIterator::rangeLength(paragraphRange.ptr()); int currentStartOffset = firstIteration ? rangeStartOffset : 0; int currentEndOffset = currentLength; if (inSameParagraph(paragraphRange->startPosition(), m_range->endPosition())) { // Determine the character offset from the end of the original search range to the end of the paragraph, // since we will want to ignore results in this area. - RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), paragraphRange->startPosition(), m_range->endPosition()); + RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer().document(), paragraphRange->startPosition(), m_range->endPosition()); currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get()); lastIteration = true; } if (currentStartOffset < currentEndOffset) { - String paragraphString = plainText(paragraphRange.get()); + String paragraphString = plainText(paragraphRange.ptr()); if (paragraphString.length() > 0) { bool foundGrammar = false; int spellingLocation = 0; @@ -345,36 +352,41 @@ String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, b Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; - checkTextOfParagraph(m_client->textChecker(), paragraphString.deprecatedCharacters(), paragraphString.length(), checkingTypes, results); - - for (unsigned i = 0; i < results.size(); i++) { - const TextCheckingResult* result = &results[i]; - if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) { - ASSERT(result->length > 0 && result->location >= 0); - spellingLocation = result->location; - misspelledWord = paragraphString.substring(result->location, result->length); + VisibleSelection currentSelection; + if (Frame* frame = paragraphRange->ownerDocument().frame()) + currentSelection = frame->selection().selection(); + checkTextOfParagraph(*m_client->textChecker(), paragraphString, checkingTypes, results, currentSelection); + + for (auto& result : results) { + if (result.type == TextCheckingTypeSpelling && result.location >= currentStartOffset && result.location + result.length <= currentEndOffset) { + ASSERT(result.length > 0); + ASSERT(result.location >= 0); + spellingLocation = result.location; + misspelledWord = paragraphString.substring(result.location, result.length); ASSERT(misspelledWord.length()); break; } - if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) { - ASSERT(result->length > 0 && result->location >= 0); + if (checkGrammar && result.type == TextCheckingTypeGrammar && result.location < currentEndOffset && result.location + result.length > currentStartOffset) { + ASSERT(result.length > 0); + ASSERT(result.location >= 0); // We can't stop after the first grammar result, since there might still be a spelling result after // it begins but before the first detail in it, but we can stop if we find a second grammar result. if (foundGrammar) break; - for (unsigned j = 0; j < result->details.size(); j++) { - const GrammarDetail* detail = &result->details[j]; - ASSERT(detail->length > 0 && detail->location >= 0); - if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) { + for (unsigned j = 0; j < result.details.size(); j++) { + const GrammarDetail* detail = &result.details[j]; + ASSERT(detail->length > 0); + ASSERT(detail->location >= 0); + if (result.location + detail->location >= currentStartOffset && result.location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result.location + detail->location < grammarDetailLocation)) { grammarDetailIndex = j; - grammarDetailLocation = result->location + detail->location; + grammarDetailLocation = result.location + detail->location; foundGrammar = true; } } if (foundGrammar) { - grammarPhraseLocation = result->location; - outGrammarDetail = result->details[grammarDetailIndex]; - badGrammarPhrase = paragraphString.substring(result->location, result->length); + grammarPhraseLocation = result.location; + outGrammarDetail = result.details[grammarDetailIndex]; + badGrammarPhrase = paragraphString.substring(result.location, result.length); ASSERT(badGrammarPhrase.length()); } } @@ -383,7 +395,7 @@ String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, b if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) { int spellingOffset = spellingLocation - currentStartOffset; if (!firstIteration) { - RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition()); + RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer().document(), m_range->startPosition(), paragraphRange->startPosition()); spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); } outIsSpelling = true; @@ -394,7 +406,7 @@ String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, b if (checkGrammar && !badGrammarPhrase.isEmpty()) { int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset; if (!firstIteration) { - RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer()->document(), m_range->startPosition(), paragraphRange->startPosition()); + RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer().document(), m_range->startPosition(), paragraphRange->startPosition()); grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); } outIsSpelling = false; @@ -407,16 +419,16 @@ String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, b if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength) break; VisiblePosition newParagraphStart = startOfNextParagraph(paragraphRange->endPosition()); - setStart(paragraphRange.get(), newParagraphStart); - setEnd(paragraphRange.get(), endOfParagraph(newParagraphStart)); + setStart(paragraphRange.ptr(), newParagraphStart); + setEnd(paragraphRange.ptr(), endOfParagraph(newParagraphStart)); firstIteration = false; totalLengthProcessed += currentLength; } return firstFoundItem; } -#endif // !PLATFORM(IOS) #if USE(GRAMMAR_CHECKING) + int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int startOffset, int endOffset, bool markAll) const { // Found some bad grammar. Find the earliest detail range that starts in our search range (if any). @@ -425,7 +437,8 @@ int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& gram int earliestDetailIndex = -1; for (unsigned i = 0; i < grammarDetails.size(); i++) { const GrammarDetail* detail = &grammarDetails[i]; - ASSERT(detail->length > 0 && detail->location >= 0); + ASSERT(detail->length > 0); + ASSERT(detail->location >= 0); int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location; @@ -439,7 +452,7 @@ int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& gram if (markAll) { RefPtr<Range> badGrammarRange = TextIterator::subrange(m_range.get(), badGrammarPhraseLocation - startOffset + detail->location, detail->length); - badGrammarRange->startContainer()->document().markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription); + badGrammarRange->startContainer().document().markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription); } // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order) @@ -458,7 +471,7 @@ String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, outGrammarDetail.location = -1; outGrammarDetail.length = 0; outGrammarDetail.guesses.clear(); - outGrammarDetail.userDescription = ""; + outGrammarDetail.userDescription = emptyString(); outGrammarPhraseOffset = 0; String firstBadGrammarPhrase; @@ -469,12 +482,11 @@ String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, TextCheckingParagraph paragraph(m_range); // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range. - int startOffset = 0; - while (startOffset < paragraph.checkingEnd()) { + for (int startOffset = 0; startOffset < paragraph.checkingEnd(); ) { Vector<GrammarDetail> grammarDetails; int badGrammarPhraseLocation = -1; int badGrammarPhraseLength = 0; - m_client->textChecker()->checkGrammarOfString(paragraph.textDeprecatedCharacters() + startOffset, paragraph.textLength() - startOffset, grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength); + m_client->textChecker()->checkGrammarOfString(StringView(paragraph.text()).substring(startOffset), grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength); if (!badGrammarPhraseLength) { ASSERT(badGrammarPhraseLocation == -1); @@ -484,7 +496,6 @@ String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, ASSERT(badGrammarPhraseLocation >= 0); badGrammarPhraseLocation += startOffset; - // Found some bad grammar. Find the earliest detail range that starts in our search range (if any). int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, paragraph.checkingStart(), paragraph.checkingEnd(), markAll); if (badGrammarIndex >= 0) { @@ -511,13 +522,12 @@ String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, return firstBadGrammarPhrase; } - bool TextCheckingHelper::isUngrammatical() const { if (!m_client) return false; - if (!m_range || m_range->collapsed(IGNORE_EXCEPTION)) + if (!m_range || m_range->collapsed()) return false; // Returns true only if the passed range exactly corresponds to a bad grammar detail range. This is analogous @@ -535,9 +545,10 @@ bool TextCheckingHelper::isUngrammatical() const // Bad grammar, but phrase (e.g. sentence) starts beyond start of range. if (grammarPhraseOffset > 0) return false; - - ASSERT(grammarDetail.location >= 0 && grammarDetail.length > 0); - + + ASSERT(grammarDetail.location >= 0); + ASSERT(grammarDetail.length > 0); + // Bad grammar, but start of detail (e.g. ungrammatical word) doesn't match start of range if (grammarDetail.location + grammarPhraseOffset) return false; @@ -554,7 +565,8 @@ bool TextCheckingHelper::isUngrammatical() const return true; } -#endif + +#endif // USE(GRAMMAR_CHECKING) Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const { @@ -565,7 +577,7 @@ Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool misspelled = false; ungrammatical = false; - if (!m_client || !m_range || m_range->collapsed(IGNORE_EXCEPTION)) + if (!m_client || !m_range || m_range->collapsed()) return guesses; // Expand the range to encompass entire paragraphs, since text checking needs that much context. @@ -575,14 +587,16 @@ Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling; - checkTextOfParagraph(m_client->textChecker(), paragraph.textDeprecatedCharacters(), paragraph.textLength(), checkingTypes, results); - - for (unsigned i = 0; i < results.size(); i++) { - const TextCheckingResult* result = &results[i]; - if (result->type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result->location, result->length)) { + VisibleSelection currentSelection; + if (Frame* frame = m_range->ownerDocument().frame()) + currentSelection = frame->selection().selection(); + checkTextOfParagraph(*m_client->textChecker(), paragraph.text(), checkingTypes, results, currentSelection); + + for (auto& result : results) { + if (result.type == TextCheckingTypeSpelling && paragraph.checkingRangeMatches(result.location, result.length)) { String misspelledWord = paragraph.checkingSubstring(); ASSERT(misspelledWord.length()); - m_client->textChecker()->getGuessesForWord(misspelledWord, String(), guesses); + m_client->textChecker()->getGuessesForWord(misspelledWord, String(), currentSelection, guesses); m_client->updateSpellingUIWithMisspelledWord(misspelledWord); misspelled = true; return guesses; @@ -591,19 +605,18 @@ Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool if (!checkGrammar) return guesses; - - for (unsigned i = 0; i < results.size(); i++) { - const TextCheckingResult* result = &results[i]; - if (result->type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result->location, result->length)) { - for (unsigned j = 0; j < result->details.size(); j++) { - const GrammarDetail* detail = &result->details[j]; - ASSERT(detail->length > 0 && detail->location >= 0); - if (paragraph.checkingRangeMatches(result->location + detail->location, detail->length)) { - String badGrammarPhrase = paragraph.textSubstring(result->location, result->length); + + for (auto& result : results) { + if (result.type == TextCheckingTypeGrammar && paragraph.isCheckingRangeCoveredBy(result.location, result.length)) { + for (auto& detail : result.details) { + ASSERT(detail.length > 0); + ASSERT(detail.location >= 0); + if (paragraph.checkingRangeMatches(result.location + detail.location, detail.length)) { + String badGrammarPhrase = paragraph.textSubstring(result.location, result.length); ASSERT(badGrammarPhrase.length()); - for (unsigned k = 0; k < detail->guesses.size(); k++) - guesses.append(detail->guesses[k]); - m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail); + for (auto& guess : detail.guesses) + guesses.append(guess); + m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, detail); ungrammatical = true; return guesses; } @@ -613,8 +626,6 @@ Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool return guesses; } - -#if !PLATFORM(IOS) void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRange) { // Use the "markAll" feature of findFirstMisspelling. Ignore the return value and the "out parameter"; @@ -633,47 +644,41 @@ void TextCheckingHelper::markAllBadGrammar() findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true); } #endif -#endif // !PLATFORM(IOS) bool TextCheckingHelper::unifiedTextCheckerEnabled() const { return m_range && WebCore::unifiedTextCheckerEnabled(m_range->ownerDocument().frame()); } -void checkTextOfParagraph(TextCheckerClient* client, const UChar* text, int length, - TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results) +void checkTextOfParagraph(TextCheckerClient& client, StringView text, TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results, const VisibleSelection& currentSelection) { #if USE(UNIFIED_TEXT_CHECKING) - results = client->checkTextOfParagraph(StringView(text, length), checkingTypes); + results = client.checkTextOfParagraph(text, checkingTypes, currentSelection); #else - Vector<TextCheckingResult> spellingResult; + UNUSED_PARAM(currentSelection); + + Vector<TextCheckingResult> mispellings; if (checkingTypes & TextCheckingTypeSpelling) - findMisspellings(client, text, length, spellingResult); + findMisspellings(client, text, mispellings); #if USE(GRAMMAR_CHECKING) - Vector<TextCheckingResult> grammarResult; + // Look for grammatical errors that occur before the first misspelling. + Vector<TextCheckingResult> grammaticalErrors; if (checkingTypes & TextCheckingTypeGrammar) { - // Only checks grammartical error before the first misspellings - int grammarCheckLength = length; - for (size_t i = 0; i < spellingResult.size(); ++i) { - if (spellingResult[i].location < grammarCheckLength) - grammarCheckLength = spellingResult[i].location; - } - - findBadGrammars(client, text, 0, grammarCheckLength, grammarResult); + unsigned grammarCheckLength = text.length(); + for (auto& mispelling : mispellings) + grammarCheckLength = std::min<unsigned>(grammarCheckLength, mispelling.location); + findGrammaticalErrors(client, text.substring(0, grammarCheckLength), grammaticalErrors); } - if (grammarResult.size()) - results.swap(grammarResult); + results = WTFMove(grammaticalErrors); #endif - if (spellingResult.size()) { - if (results.isEmpty()) - results.swap(spellingResult); - else - results.appendVector(spellingResult); - } -#endif + if (results.isEmpty()) + results = WTFMove(mispellings); + else + results.appendVector(mispellings); +#endif // USE(UNIFIED_TEXT_CHECKING) } bool unifiedTextCheckerEnabled(const Frame* frame) diff --git a/Source/WebCore/editing/TextCheckingHelper.h b/Source/WebCore/editing/TextCheckingHelper.h index a90c9c8b3..d8df24e66 100644 --- a/Source/WebCore/editing/TextCheckingHelper.h +++ b/Source/WebCore/editing/TextCheckingHelper.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006, 2007, 2008, 2014 Apple Inc. All rights reserved. * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) * * This library is free software; you can redistribute it and/or @@ -18,18 +18,17 @@ * Boston, MA 02110-1301, USA. */ -#ifndef TextCheckingHelper_h -#define TextCheckingHelper_h +#pragma once #include "EditorClient.h" -#include "ExceptionCode.h" +#include "ExceptionOr.h" #include "TextChecking.h" -#include <wtf/text/WTFString.h> namespace WebCore { -class Range; class Position; +class Range; + struct TextCheckingResult; class TextCheckingParagraph { @@ -40,17 +39,18 @@ public: int rangeLength() const; PassRefPtr<Range> subrange(int characterOffset, int characterCount) const; - int offsetTo(const Position&, ExceptionCode&) const; + ExceptionOr<int> offsetTo(const Position&) const; void expandRangeToNextEnd(); + // FIXME: Consider changing this to return a StringView. + const String& text() const; + + // FIXME: Consider removing these and just having the caller use text() directly. int textLength() const { return text().length(); } String textSubstring(unsigned pos, unsigned len = UINT_MAX) const { return text().substring(pos, len); } - const UChar* textDeprecatedCharacters() const { return text().deprecatedCharacters(); } UChar textCharAt(int index) const { return text()[static_cast<unsigned>(index)]; } bool isEmpty() const; - bool isTextEmpty() const { return text().isEmpty(); } - bool isRangeEmpty() const { return checkingStart() >= checkingEnd(); } int checkingStart() const; int checkingEnd() const; @@ -64,9 +64,7 @@ public: private: void invalidateParagraphRangeValues(); - PassRefPtr<Range> checkingRange() const { return m_checkingRange; } PassRefPtr<Range> offsetAsRange() const; - const String& text() const; RefPtr<Range> m_checkingRange; mutable RefPtr<Range> m_paragraphRange; @@ -103,11 +101,8 @@ private: #endif }; -void checkTextOfParagraph(TextCheckerClient*, const UChar* text, int length, - TextCheckingTypeMask checkingTypes, Vector<TextCheckingResult>& results); +void checkTextOfParagraph(TextCheckerClient&, StringView, TextCheckingTypeMask, Vector<TextCheckingResult>&, const VisibleSelection& currentSelection); bool unifiedTextCheckerEnabled(const Frame*); } // namespace WebCore - -#endif // TextCheckingHelper_h diff --git a/Source/WebCore/editing/TextGranularity.h b/Source/WebCore/editing/TextGranularity.h index aaa3d7d11..5e201fe8e 100644 --- a/Source/WebCore/editing/TextGranularity.h +++ b/Source/WebCore/editing/TextGranularity.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2004 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TextGranularity_h -#define TextGranularity_h +#pragma once namespace WebCore { @@ -36,15 +35,11 @@ enum TextGranularity { SentenceGranularity, LineGranularity, ParagraphGranularity, -#if PLATFORM(IOS) DocumentGranularity, -#endif SentenceBoundary, LineBoundary, ParagraphBoundary, DocumentBoundary }; -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/editing/TextInsertionBaseCommand.cpp b/Source/WebCore/editing/TextInsertionBaseCommand.cpp index fd7219d60..cf9c2624a 100644 --- a/Source/WebCore/editing/TextInsertionBaseCommand.cpp +++ b/Source/WebCore/editing/TextInsertionBaseCommand.cpp @@ -35,21 +35,21 @@ namespace WebCore { -TextInsertionBaseCommand::TextInsertionBaseCommand(Document& document) - : CompositeEditCommand(document) +TextInsertionBaseCommand::TextInsertionBaseCommand(Document& document, EditAction editingAction) + : CompositeEditCommand(document, editingAction) { } -void TextInsertionBaseCommand::applyTextInsertionCommand(Frame* frame, PassRefPtr<TextInsertionBaseCommand> command, const VisibleSelection& selectionForInsertion, const VisibleSelection& endingSelection) +void TextInsertionBaseCommand::applyTextInsertionCommand(Frame* frame, TextInsertionBaseCommand& command, const VisibleSelection& selectionForInsertion, const VisibleSelection& endingSelection) { bool changeSelection = selectionForInsertion != endingSelection; if (changeSelection) { - command->setStartingSelection(selectionForInsertion); - command->setEndingSelection(selectionForInsertion); + command.setStartingSelection(selectionForInsertion); + command.setEndingSelection(selectionForInsertion); } - applyCommand(command); + applyCommand(&command); if (changeSelection) { - command->setEndingSelection(endingSelection); + command.setEndingSelection(endingSelection); frame->selection().setSelection(endingSelection); } } @@ -63,9 +63,9 @@ String dispatchBeforeTextInsertedEvent(const String& text, const VisibleSelectio if (Node* startNode = selectionForInsertion.start().containerNode()) { if (startNode->rootEditableElement()) { // Send BeforeTextInsertedEvent. The event handler will update text if necessary. - RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text); - startNode->rootEditableElement()->dispatchEvent(evt, IGNORE_EXCEPTION); - newText = evt->text(); + Ref<BeforeTextInsertedEvent> event = BeforeTextInsertedEvent::create(text); + startNode->rootEditableElement()->dispatchEvent(event); + newText = event->text(); } } return newText; @@ -77,8 +77,8 @@ bool canAppendNewLineFeedToSelection(const VisibleSelection& selection) if (!node) return false; - RefPtr<BeforeTextInsertedEvent> event = BeforeTextInsertedEvent::create(String("\n")); - node->dispatchEvent(event, IGNORE_EXCEPTION); + Ref<BeforeTextInsertedEvent> event = BeforeTextInsertedEvent::create(String("\n")); + node->dispatchEvent(event); return event->text().length(); } diff --git a/Source/WebCore/editing/TextInsertionBaseCommand.h b/Source/WebCore/editing/TextInsertionBaseCommand.h index e549b5c17..10d9adb08 100644 --- a/Source/WebCore/editing/TextInsertionBaseCommand.h +++ b/Source/WebCore/editing/TextInsertionBaseCommand.h @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TextInsertionBaseCommand_h -#define TextInsertionBaseCommand_h +#pragma once #include "CompositeEditCommand.h" #include <wtf/text/WTFString.h> @@ -39,8 +38,8 @@ public: virtual ~TextInsertionBaseCommand() { }; protected: - explicit TextInsertionBaseCommand(Document&); - static void applyTextInsertionCommand(Frame*, PassRefPtr<TextInsertionBaseCommand>, const VisibleSelection& selectionForInsertion, const VisibleSelection& endingSelection); + explicit TextInsertionBaseCommand(Document&, EditAction = EditActionUnspecified); + static void applyTextInsertionCommand(Frame*, TextInsertionBaseCommand&, const VisibleSelection& selectionForInsertion, const VisibleSelection& endingSelection); }; String dispatchBeforeTextInsertedEvent(const String& text, const VisibleSelection& selectionForInsertion, bool insertionIsForUpdatingComposition); @@ -66,6 +65,4 @@ void forEachLineInString(const String& string, const LineOperation& operation) } } -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/editing/TextIterator.cpp b/Source/WebCore/editing/TextIterator.cpp index 8baee8525..3cedb1c59 100644 --- a/Source/WebCore/editing/TextIterator.cpp +++ b/Source/WebCore/editing/TextIterator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All rights reserved. + * Copyright (C) 2004-2014 Apple Inc. All rights reserved. * Copyright (C) 2005 Alexey Proskuryakov. * * Redistribution and use in source and binary forms, with or without @@ -11,10 +11,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -28,11 +28,19 @@ #include "TextIterator.h" #include "Document.h" -#include "ExceptionCodePlaceholder.h" -#include "Font.h" +#include "FontCascade.h" #include "Frame.h" +#include "HTMLBodyElement.h" #include "HTMLElement.h" +#include "HTMLFrameOwnerElement.h" +#include "HTMLInputElement.h" +#include "HTMLLegendElement.h" +#include "HTMLMeterElement.h" #include "HTMLNames.h" +#include "HTMLParagraphElement.h" +#include "HTMLProgressElement.h" +#include "HTMLSlotElement.h" +#include "HTMLTextAreaElement.h" #include "HTMLTextFormControlElement.h" #include "InlineTextBox.h" #include "NodeTraversal.h" @@ -43,18 +51,21 @@ #include "RenderTextControl.h" #include "RenderTextFragment.h" #include "ShadowRoot.h" +#include "SimpleLineLayout.h" +#include "SimpleLineLayoutResolver.h" #include "TextBoundaries.h" -#include "TextBreakIterator.h" +#include <wtf/text/TextBreakIterator.h> #include "TextControlInnerElements.h" #include "VisiblePosition.h" #include "VisibleUnits.h" #include "htmlediting.h" #include <wtf/text/CString.h> +#include <wtf/text/StringBuilder.h> #include <wtf/unicode/CharacterNames.h> #if !UCONFIG_NO_COLLATION -#include "TextBreakIteratorInternalICU.h" #include <unicode/usearch.h> +#include <wtf/text/TextBreakIteratorInternalICU.h> #endif using namespace WTF::Unicode; @@ -76,9 +87,9 @@ public: ~SearchBuffer(); // Returns number of characters appended; guaranteed to be in the range [1, length]. - size_t append(const UChar*, size_t length); + size_t append(StringView); bool needsMoreContext() const; - void prependContext(const UChar*, size_t length); + void prependContext(StringView); void reachedBreak(); // Result is the size in characters of what was found. @@ -91,8 +102,10 @@ public: private: bool isBadMatch(const UChar*, size_t length) const; bool isWordStartMatch(size_t start, size_t length) const; + bool isWordEndMatch(size_t start, size_t length) const; - String m_target; + const String m_target; + const StringView::UpconvertedCharacters m_targetCharacters; FindOptions m_options; Vector<UChar> m_buffer; @@ -101,7 +114,7 @@ private: bool m_atBreak; bool m_needsMoreContext; - bool m_targetRequiresKanaWorkaround; + const bool m_targetRequiresKanaWorkaround; Vector<UChar> m_normalizedTarget; mutable Vector<UChar> m_normalizedMatch; @@ -174,186 +187,193 @@ unsigned BitStack::size() const // -------- -#if !ASSERT_DISABLED - -static unsigned depthCrossingShadowBoundaries(Node* node) -{ - unsigned depth = 0; - for (Node* parent = node->parentOrShadowHostNode(); parent; parent = parent->parentOrShadowHostNode()) - ++depth; - return depth; -} - -#endif - // This function is like Range::pastLastNode, except for the fact that it can climb up out of shadow trees. -static Node* nextInPreOrderCrossingShadowBoundaries(Node* rangeEndContainer, int rangeEndOffset) +static Node* nextInPreOrderCrossingShadowBoundaries(Node& rangeEndContainer, int rangeEndOffset) { - if (!rangeEndContainer) - return 0; - if (rangeEndOffset >= 0 && !rangeEndContainer->offsetInCharacters()) { - if (Node* next = rangeEndContainer->childNode(rangeEndOffset)) + if (rangeEndOffset >= 0 && !rangeEndContainer.offsetInCharacters()) { + if (Node* next = rangeEndContainer.traverseToChildAt(rangeEndOffset)) return next; } - for (Node* node = rangeEndContainer; node; node = node->parentOrShadowHostNode()) { + for (Node* node = &rangeEndContainer; node; node = node->parentOrShadowHostNode()) { if (Node* next = node->nextSibling()) return next; } - return 0; + return nullptr; } -// -------- - -static inline bool fullyClipsContents(Node* node) +static inline bool fullyClipsContents(Node& node) { - RenderObject* renderer = node->renderer(); - if (!renderer || !renderer->isBox() || !renderer->hasOverflowClip()) + auto* renderer = node.renderer(); + if (!renderer) { + if (!is<Element>(node)) + return false; + return !downcast<Element>(node).hasDisplayContents(); + } + if (!is<RenderBox>(*renderer)) + return false; + auto& box = downcast<RenderBox>(*renderer); + if (!box.hasOverflowClip()) return false; - return toRenderBox(renderer)->size().isEmpty(); + + // Quirk to keep copy/paste in the CodeMirror editor version used in Jenkins working. + if (is<HTMLTextAreaElement>(node)) + return box.size().isEmpty(); + + return box.contentSize().isEmpty(); } -static inline bool ignoresContainerClip(Node* node) +static inline bool ignoresContainerClip(Node& node) { - RenderObject* renderer = node->renderer(); + auto* renderer = node.renderer(); if (!renderer || renderer->isTextOrLineBreak()) return false; return renderer->style().hasOutOfFlowPosition(); } -static void pushFullyClippedState(BitStack& stack, Node* node) +static void pushFullyClippedState(BitStack& stack, Node& node) { - ASSERT(stack.size() == depthCrossingShadowBoundaries(node)); - // Push true if this node full clips its contents, or if a parent already has fully // clipped and this is not a node that ignores its container's clip. stack.push(fullyClipsContents(node) || (stack.top() && !ignoresContainerClip(node))); } -static void setUpFullyClippedStack(BitStack& stack, Node* node) +static void setUpFullyClippedStack(BitStack& stack, Node& node) { // Put the nodes in a vector so we can iterate in reverse order. + // FIXME: This (and TextIterator in general) should use ComposedTreeIterator. Vector<Node*, 100> ancestry; - for (Node* parent = node->parentOrShadowHostNode(); parent; parent = parent->parentOrShadowHostNode()) + for (Node* parent = node.parentOrShadowHostNode(); parent; parent = parent->parentOrShadowHostNode()) ancestry.append(parent); // Call pushFullyClippedState on each node starting with the earliest ancestor. size_t size = ancestry.size(); for (size_t i = 0; i < size; ++i) - pushFullyClippedState(stack, ancestry[size - i - 1]); + pushFullyClippedState(stack, *ancestry[size - i - 1]); pushFullyClippedState(stack, node); +} - ASSERT(stack.size() == 1 + depthCrossingShadowBoundaries(node)); +static bool isClippedByFrameAncestor(const Document& document, TextIteratorBehavior behavior) +{ + if (!(behavior & TextIteratorClipsToFrameAncestors)) + return false; + + for (auto* owner = document.ownerElement(); owner; owner = owner->document().ownerElement()) { + BitStack ownerClipStack; + setUpFullyClippedStack(ownerClipStack, *owner); + if (ownerClipStack.top()) + return true; + } + return false; } - + +// FIXME: editingIgnoresContent and isRendererReplacedElement try to do the same job. +// It's not good to have both of them. bool isRendererReplacedElement(RenderObject* renderer) { if (!renderer) return false; -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - if (renderer->isImage() || renderer->isMedia()) - return true; - if (renderer->isWidget()) { - if (renderer->node() && renderer->node()->isElementNode()) { - Element* element = toElement(renderer->node()); - if (element->hasTagName(videoTag) || !element->hasTagName(audioTag)) - return false; // See <rdar://problem/6893793>. - } - return true; - } -#else - if (renderer->isImage() || renderer->isWidget() || renderer->isMedia()) - return true; + bool isAttachment = false; +#if ENABLE(ATTACHMENT_ELEMENT) + isAttachment = renderer->isAttachment(); #endif - - if (renderer->node() && renderer->node()->isElementNode()) { - Element* element = toElement(renderer->node()); - if (element->isFormControlElement() || element->hasTagName(legendTag) - || element->hasTagName(meterTag) || element->hasTagName(progressTag)) + if (renderer->isImage() || renderer->isWidget() || renderer->isMedia() || isAttachment) + return true; + + if (is<Element>(renderer->node())) { + Element& element = downcast<Element>(*renderer->node()); + if (is<HTMLFormControlElement>(element) || is<HTMLLegendElement>(element) || is<HTMLProgressElement>(element) || element.hasTagName(meterTag)) return true; - - if (equalIgnoringCase(element->getAttribute(roleAttr), "img")) + if (equalLettersIgnoringASCIICase(element.attributeWithoutSynchronization(roleAttr), "img")) return true; } - + return false; } // -------- -TextIterator::TextIterator(const Range* r, TextIteratorBehavior behavior) - : m_startContainer(0) - , m_startOffset(0) - , m_endContainer(0) - , m_endOffset(0) - , m_positionNode(0) - , m_textCharacters(0) - , m_textLength(0) - , m_remainingTextBox(0) - , m_firstLetterText(0) - , m_sortedTextBoxesPosition(0) - , m_emitsCharactersBetweenAllVisiblePositions(behavior & TextIteratorEmitsCharactersBetweenAllVisiblePositions) - , m_entersTextControls(behavior & TextIteratorEntersTextControls) - , m_emitsTextWithoutTranscoding(behavior & TextIteratorEmitsTextsWithoutTranscoding) - , m_emitsOriginalText(behavior & TextIteratorEmitsOriginalText) - , m_handledFirstLetter(false) - , m_ignoresStyleVisibility(behavior & TextIteratorIgnoresStyleVisibility) - , m_emitsObjectReplacementCharacters(behavior & TextIteratorEmitsObjectReplacementCharacters) - , m_stopsOnFormControls(behavior & TextIteratorStopsOnFormControls) - , m_shouldStop(false) - , m_emitsImageAltText(behavior & TextIteratorEmitsImageAltText) -{ - if (!r) - return; +inline void TextIteratorCopyableText::reset() +{ + m_singleCharacter = 0; + m_string = String(); + m_offset = 0; + m_length = 0; +} + +inline void TextIteratorCopyableText::set(String&& string) +{ + m_singleCharacter = 0; + m_string = WTFMove(string); + m_offset = 0; + m_length = m_string.length(); +} + +inline void TextIteratorCopyableText::set(String&& string, unsigned offset, unsigned length) +{ + ASSERT(offset < string.length()); + ASSERT(length); + ASSERT(length <= string.length() - offset); + + m_singleCharacter = 0; + m_string = WTFMove(string); + m_offset = offset; + m_length = length; +} + +inline void TextIteratorCopyableText::set(UChar singleCharacter) +{ + m_singleCharacter = singleCharacter; + m_string = String(); + m_offset = 0; + m_length = 0; +} - r->ownerDocument().updateLayoutIgnorePendingStylesheets(); +void TextIteratorCopyableText::appendToStringBuilder(StringBuilder& builder) const +{ + if (m_singleCharacter) + builder.append(m_singleCharacter); + else + builder.append(m_string, m_offset, m_length); +} + +// -------- - // get and validate the range endpoints - Node* startContainer = r->startContainer(); - if (!startContainer) +TextIterator::TextIterator(const Range* range, TextIteratorBehavior behavior) + : m_behavior(behavior) +{ + // FIXME: Only m_positionNode above needs to be initialized if range is null. + if (!range) return; - int startOffset = r->startOffset(); - Node* endContainer = r->endContainer(); - int endOffset = r->endOffset(); + + range->ownerDocument().updateLayoutIgnorePendingStylesheets(); + + m_startContainer = &range->startContainer(); // Callers should be handing us well-formed ranges. If we discover that this isn't // the case, we could consider changing this assertion to an early return. - ASSERT(r->boundaryPointsValid()); + ASSERT(range->boundaryPointsValid()); - // remember range - this does not change - m_startContainer = startContainer; - m_startOffset = startOffset; - m_endContainer = endContainer; - m_endOffset = endOffset; + m_startOffset = range->startOffset(); + m_endContainer = &range->endContainer(); + m_endOffset = range->endOffset(); - // set up the current node for processing - m_node = r->firstNode(); + // Set up the current node for processing. + m_node = range->firstNode(); if (!m_node) return; - setUpFullyClippedStack(m_fullyClippedStack, m_node); - m_offset = m_node == m_startContainer ? m_startOffset : 0; - m_handledNode = false; - m_handledChildren = false; - // calculate first out of bounds node - m_pastEndNode = nextInPreOrderCrossingShadowBoundaries(endContainer, endOffset); + if (isClippedByFrameAncestor(m_node->document(), m_behavior)) + return; - // initialize node processing state - m_needsAnotherNewline = false; - m_textBox = 0; + setUpFullyClippedStack(m_fullyClippedStack, *m_node); - // initialize record of previous node processing - m_hasEmitted = false; - m_lastTextNode = 0; - m_lastTextNodeEndedWithCollapsedSpace = false; - m_lastCharacter = 0; + m_offset = m_node == m_startContainer ? m_startOffset : 0; + + m_pastEndNode = nextInPreOrderCrossingShadowBoundaries(*m_endContainer, m_endOffset); -#ifndef NDEBUG - // need this just because of the assert in advance() m_positionNode = m_node; -#endif - // identify the first run advance(); } @@ -361,33 +381,95 @@ TextIterator::~TextIterator() { } +static HTMLSlotElement* assignedAuthorSlot(Node& node) +{ + auto* slot = node.assignedSlot(); + if (!slot || slot->containingShadowRoot()->mode() == ShadowRootMode::UserAgent) + return nullptr; + return slot; +} + +static ShadowRoot* authorShadowRoot(Node& node) +{ + auto* shadowRoot = node.shadowRoot(); + if (!shadowRoot || shadowRoot->mode() == ShadowRootMode::UserAgent) + return nullptr; + return shadowRoot; +} + +// FIXME: Use ComposedTreeIterator instead. These functions are more expensive because they might do O(n) work. +static inline Node* firstChildInFlatTreeIgnoringUserAgentShadow(Node& node) +{ + if (auto* shadowRoot = authorShadowRoot(node)) + return shadowRoot->firstChild(); + if (is<HTMLSlotElement>(node)) { + if (auto* assignedNodes = downcast<HTMLSlotElement>(node).assignedNodes()) + return assignedNodes->at(0); + } + return node.firstChild(); +} + +static inline Node* nextSiblingInFlatTreeIgnoringUserAgentShadow(Node& node) +{ + if (auto* slot = assignedAuthorSlot(node)) { + auto* assignedNodes = slot->assignedNodes(); + ASSERT(assignedNodes); + auto nodeIndex = assignedNodes->find(&node); + ASSERT(nodeIndex != notFound); + if (assignedNodes->size() > nodeIndex + 1) + return assignedNodes->at(nodeIndex + 1); + return nullptr; + } + return node.nextSibling(); +} + +static inline Node* firstChild(TextIteratorBehavior options, Node& node) +{ + if (UNLIKELY(options & TextIteratorTraversesFlatTree)) + return firstChildInFlatTreeIgnoringUserAgentShadow(node); + return node.firstChild(); +} + +static inline Node* nextSibling(TextIteratorBehavior options, Node& node) +{ + if (UNLIKELY(options & TextIteratorTraversesFlatTree)) + return nextSiblingInFlatTreeIgnoringUserAgentShadow(node); + return node.nextSibling(); +} + +static inline Node* parentNodeOrShadowHost(TextIteratorBehavior options, Node& node) +{ + if (UNLIKELY(options & TextIteratorTraversesFlatTree)) + return node.parentInComposedTree(); + return node.parentOrShadowHostNode(); +} + void TextIterator::advance() { - if (m_shouldStop) - return; + ASSERT(!atEnd()); // reset the run information - m_positionNode = 0; - m_textLength = 0; + m_positionNode = nullptr; + m_copyableText.reset(); + m_text = StringView(); // handle remembered node that needed a newline after the text node's newline - if (m_needsAnotherNewline) { + if (m_nodeForAdditionalNewline) { // Emit the extra newline, and position it *inside* m_node, after m_node's // contents, in case it's a block, in the same way that we position the first - // newline. The range for the emitted newline should start where the line + // newline. The range for the emitted newline should start where the line // break begins. // FIXME: It would be cleaner if we emitted two newlines during the last // iteration, instead of using m_needsAnotherNewline. - Node* baseNode = m_node->lastChild() ? m_node->lastChild() : m_node; - emitCharacter('\n', baseNode->parentNode(), baseNode, 1, 1); - m_needsAnotherNewline = false; + emitCharacter('\n', *m_nodeForAdditionalNewline->parentNode(), m_nodeForAdditionalNewline, 1, 1); + m_nodeForAdditionalNewline = nullptr; return; } if (!m_textBox && m_remainingTextBox) { m_textBox = m_remainingTextBox; - m_remainingTextBox = 0; - m_firstLetterText = 0; + m_remainingTextBox = nullptr; + m_firstLetterText = nullptr; m_offset = 0; } // handle remembered text box @@ -398,27 +480,24 @@ 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 // precedes the element - if (m_node == m_endContainer && m_endOffset == 0) { + if (m_node == m_endContainer && !m_endOffset) { representNodeOffsetZero(); - m_node = 0; + m_node = nullptr; return; } - RenderObject* renderer = m_node->renderer(); + auto* renderer = m_node->renderer(); if (!renderer) { m_handledNode = true; - m_handledChildren = true; + m_handledChildren = !((m_behavior & TextIteratorTraversesFlatTree) && is<Element>(*m_node) && downcast<Element>(*m_node).hasDisplayContents()); } else { // handle current node according to its type if (!m_handledNode) { - if (renderer->isText() && m_node->nodeType() == Node::TEXT_NODE) // FIXME: What about CDATA_SECTION_NODE? + if (renderer->isText() && m_node->isTextNode()) m_handledNode = handleTextNode(); else if (isRendererReplacedElement(renderer)) m_handledNode = handleReplacedElement(); @@ -431,28 +510,29 @@ void TextIterator::advance() // find a new current node to handle in depth-first manner, // calling exitNode() as we come back thru a parent node - Node* next = m_handledChildren ? 0 : m_node->firstChild(); + Node* next = m_handledChildren ? nullptr : firstChild(m_behavior, *m_node); m_offset = 0; if (!next) { - next = m_node->nextSibling(); + next = nextSibling(m_behavior, *m_node); if (!next) { - bool pastEnd = NodeTraversal::next(m_node) == m_pastEndNode; - Node* parentNode = m_node->parentOrShadowHostNode(); + bool pastEnd = NodeTraversal::next(*m_node) == m_pastEndNode; + Node* parentNode = parentNodeOrShadowHost(m_behavior, *m_node); while (!next && parentNode) { - if ((pastEnd && parentNode == m_endContainer) || m_endContainer->isDescendantOf(parentNode)) + if ((pastEnd && parentNode == m_endContainer) || m_endContainer->isDescendantOf(*parentNode)) return; bool haveRenderer = m_node->renderer(); + Node* exitedNode = m_node; m_node = parentNode; m_fullyClippedStack.pop(); - parentNode = m_node->parentOrShadowHostNode(); + parentNode = parentNodeOrShadowHost(m_behavior, *m_node); if (haveRenderer) - exitNode(); + exitNode(exitedNode); if (m_positionNode) { m_handledNode = true; m_handledChildren = true; return; } - next = m_node->nextSibling(); + next = nextSibling(m_behavior, *m_node); } } m_fullyClippedStack.pop(); @@ -461,11 +541,11 @@ void TextIterator::advance() // set the new current node m_node = next; if (m_node) - pushFullyClippedState(m_fullyClippedStack, m_node); + pushFullyClippedState(m_fullyClippedStack, *m_node); m_handledNode = false; m_handledChildren = false; m_handledFirstLetter = false; - m_firstLetterText = 0; + m_firstLetterText = nullptr; // how would this ever be? if (m_positionNode) @@ -473,122 +553,179 @@ void TextIterator::advance() } } -UChar TextIterator::characterAt(unsigned index) const +static bool hasVisibleTextNode(RenderText& renderer) { - ASSERT_WITH_SECURITY_IMPLICATION(index < static_cast<unsigned>(length())); - if (!(index < static_cast<unsigned>(length()))) - return 0; - - if (!m_textCharacters) - return string()[startOffset() + index]; + if (renderer.style().visibility() == VISIBLE) + return true; + if (is<RenderTextFragment>(renderer)) { + if (auto firstLetter = downcast<RenderTextFragment>(renderer).firstLetter()) { + if (firstLetter->style().visibility() == VISIBLE) + return true; + } + } + return false; +} - return m_textCharacters[index]; +static unsigned textNodeOffsetInFlow(const Text& firstTextNodeInRange) +{ + // Calculate the text offset for simple lines. + RenderObject* renderer = firstTextNodeInRange.renderer(); + if (!renderer) + return 0; + unsigned textOffset = 0; + for (renderer = renderer->previousSibling(); renderer; renderer = renderer->previousSibling()) { + if (is<RenderText>(renderer)) + textOffset += downcast<RenderText>(renderer)->textLength(); + } + return textOffset; } -void TextIterator::appendTextToStringBuilder(StringBuilder& builder) const +static bool isNewLineOrTabCharacter(UChar character) { - if (!m_textCharacters) - builder.append(string(), startOffset(), length()); - else - builder.append(characters(), length()); + return character == '\n' || character == '\t'; } bool TextIterator::handleTextNode() { - if (m_fullyClippedStack.top() && !m_ignoresStyleVisibility) + Text& textNode = downcast<Text>(*m_node); + + if (m_fullyClippedStack.top() && !(m_behavior & TextIteratorIgnoresStyleVisibility)) return false; - RenderText* renderer = toRenderText(m_node->renderer()); - - m_lastTextNode = m_node; - String str = renderer->text(); + auto& renderer = *textNode.renderer(); + m_lastTextNode = &textNode; + String rendererText = renderer.text(); // handle pre-formatted text - if (!renderer->style().collapseWhiteSpace()) { + if (!renderer.style().collapseWhiteSpace()) { int runStart = m_offset; if (m_lastTextNodeEndedWithCollapsedSpace && hasVisibleTextNode(renderer)) { - emitCharacter(' ', m_node, 0, runStart, runStart); + emitCharacter(' ', textNode, nullptr, runStart, runStart); return false; } - if (!m_handledFirstLetter && renderer->isTextFragment() && !m_offset) { - handleTextNodeFirstLetter(static_cast<RenderTextFragment*>(renderer)); + if (!m_handledFirstLetter && is<RenderTextFragment>(renderer) && !m_offset) { + handleTextNodeFirstLetter(downcast<RenderTextFragment>(renderer)); if (m_firstLetterText) { String firstLetter = m_firstLetterText->text(); - emitText(m_node, m_firstLetterText, m_offset, m_offset + firstLetter.length()); - m_firstLetterText = 0; - m_textBox = 0; + emitText(textNode, *m_firstLetterText, m_offset, m_offset + firstLetter.length()); + m_firstLetterText = nullptr; + m_textBox = nullptr; return false; } } - if (renderer->style().visibility() != VISIBLE && !m_ignoresStyleVisibility) + if (renderer.style().visibility() != VISIBLE && !(m_behavior & TextIteratorIgnoresStyleVisibility)) return false; - int strLength = str.length(); - int end = (m_node == m_endContainer) ? m_endOffset : INT_MAX; - int runEnd = std::min(strLength, end); + int rendererTextLength = rendererText.length(); + int end = (&textNode == m_endContainer) ? m_endOffset : INT_MAX; + int runEnd = std::min(rendererTextLength, end); if (runStart >= runEnd) return true; - emitText(m_node, runStart, runEnd); + emitText(textNode, renderer, runStart, runEnd); return true; } - if (renderer->simpleLineLayout()) { - if (renderer->style().visibility() != VISIBLE && !m_ignoresStyleVisibility) + if (const auto* layout = renderer.simpleLineLayout()) { + if (renderer.style().visibility() != VISIBLE && !(m_behavior & TextIteratorIgnoresStyleVisibility)) return true; - // This code aims to produce same results as handleTextBox() below so test results don't change. It does not make much logical sense. - unsigned runEnd = m_offset; - unsigned runStart = m_offset; - while (runEnd < str.length() && (isCollapsibleWhitespace(str[runEnd]) || str[runEnd] == '\t')) - ++runEnd; - bool addSpaceForPrevious = m_lastTextNodeEndedWithCollapsedSpace && m_lastCharacter && !isCollapsibleWhitespace(m_lastCharacter); - if (runEnd > runStart || addSpaceForPrevious) { - if (runEnd == str.length()) { - m_lastTextNodeEndedWithCollapsedSpace = true; - return true; + ASSERT(renderer.parent()); + ASSERT(is<RenderBlockFlow>(*renderer.parent())); + const auto& blockFlow = downcast<RenderBlockFlow>(*renderer.parent()); + // Use the simple layout runs to iterate over the text content. + bool isNewTextNode = m_previousSimpleTextNodeInFlow && m_previousSimpleTextNodeInFlow != &textNode; + // Simple line layout run positions are all absolute to the parent flow. + // Offsetting is required when multiple renderers are present. + m_accumulatedSimpleTextLengthInFlow += isNewTextNode ? m_previousSimpleTextNodeInFlow->renderer()->text()->length() : 0; + m_previousSimpleTextNodeInFlow = &textNode; + + unsigned endPosition = (m_node == m_endContainer) ? static_cast<unsigned>(m_endOffset) : rendererText.length(); + if (!m_flowRunResolverCache || &m_flowRunResolverCache->flow() != &blockFlow) { + m_accumulatedSimpleTextLengthInFlow = m_flowRunResolverCache ? 0 : textNodeOffsetInFlow(textNode); + m_flowRunResolverCache = std::make_unique<SimpleLineLayout::RunResolver>(blockFlow, *layout); + } + // Skip to m_offset position. + auto range = m_flowRunResolverCache->rangeForRenderer(renderer); + auto it = range.begin(); + auto end = range.end(); + while (it != end && (*it).end() <= (static_cast<unsigned>(m_offset) + m_accumulatedSimpleTextLengthInFlow)) + ++it; + if (m_nextRunNeedsWhitespace && rendererText[m_offset - 1] == '\n') { + emitCharacter(' ', textNode, nullptr, m_offset, m_offset + 1); + return it == end; + } + if (it == end) { + // Collapsed trailing whitespace. + m_offset = endPosition; + m_lastTextNodeEndedWithCollapsedSpace = true; + return true; + } + if (m_nextRunNeedsWhitespace) { + emitCharacter(' ', textNode, nullptr, m_offset, m_offset + 1); + return false; + } + const auto run = *it; + ASSERT(run.end() - run.start() <= rendererText.length()); + // contentStart skips leading whitespace. + unsigned contentStart = std::max<unsigned>(m_offset, run.start() - m_accumulatedSimpleTextLengthInFlow); + unsigned contentEnd = std::min(endPosition, run.end() - m_accumulatedSimpleTextLengthInFlow); + ASSERT_WITH_SECURITY_IMPLICATION(contentStart <= contentEnd); + // Check if whitespace adjustment is needed when crossing renderer boundary. + if (isNewTextNode) { + bool lastCharacterIsNotWhitespace = m_lastCharacter && !renderer.style().isCollapsibleWhiteSpace(m_lastCharacter); + bool addTrailingWhitespaceForPrevious = m_lastTextNodeEndedWithCollapsedSpace && lastCharacterIsNotWhitespace; + bool leadingWhitespaceIsNeededForCurrent = contentStart > static_cast<unsigned>(m_offset) && lastCharacterIsNotWhitespace; + if (addTrailingWhitespaceForPrevious || leadingWhitespaceIsNeededForCurrent) { + emitCharacter(' ', textNode, nullptr, m_offset, m_offset + 1); + return false; } - bool addSpaceForCurrent = runStart || (m_lastCharacter && !isCollapsibleWhitespace(m_lastCharacter)); - if (addSpaceForCurrent || addSpaceForPrevious) { - emitCharacter(' ', m_node, 0, runStart, runEnd); - m_offset = runEnd; + } + // \n \t single whitespace characters need replacing so that the new line/tab characters don't show up. + unsigned stopPosition = contentStart; + while (stopPosition < contentEnd && !isNewLineOrTabCharacter(rendererText[stopPosition])) + ++stopPosition; + // Emit the text up to the new line/tab character. + if (stopPosition < contentEnd) { + if (stopPosition == contentStart) { + emitCharacter(' ', textNode, nullptr, contentStart, contentStart + 1); + m_offset = contentStart + 1; return false; } - runStart = runEnd; + emitText(textNode, renderer, contentStart, stopPosition); + m_offset = stopPosition + 1; + m_nextRunNeedsWhitespace = true; + return false; } - while (runEnd < str.length() && !isCollapsibleWhitespace(str[runEnd])) - ++runEnd; - if (runStart < str.length()) - emitText(m_node, renderer, runStart, runEnd); - m_offset = runEnd; - return runEnd == str.length(); + emitText(textNode, renderer, contentStart, contentEnd); + // When line ending with collapsed whitespace is present, we need to carry over one whitespace: foo(end of line)bar -> foo bar (otherwise we would end up with foobar). + m_nextRunNeedsWhitespace = run.isEndOfLine() && contentEnd < endPosition && renderer.style().isCollapsibleWhiteSpace(rendererText[contentEnd]); + m_offset = contentEnd; + return static_cast<unsigned>(m_offset) == endPosition; } - if (renderer->firstTextBox()) - m_textBox = renderer->firstTextBox(); + if (renderer.firstTextBox()) + m_textBox = renderer.firstTextBox(); - bool shouldHandleFirstLetter = !m_handledFirstLetter && renderer->isTextFragment() && !m_offset; + bool shouldHandleFirstLetter = !m_handledFirstLetter && is<RenderTextFragment>(renderer) && !m_offset; if (shouldHandleFirstLetter) - handleTextNodeFirstLetter(static_cast<RenderTextFragment*>(renderer)); + handleTextNodeFirstLetter(downcast<RenderTextFragment>(renderer)); - if (!renderer->firstTextBox() && str.length() > 0 && !shouldHandleFirstLetter) { - if (renderer->style().visibility() != VISIBLE && !m_ignoresStyleVisibility) + if (!renderer.firstTextBox() && rendererText.length() && !shouldHandleFirstLetter) { + if (renderer.style().visibility() != VISIBLE && !(m_behavior & TextIteratorIgnoresStyleVisibility)) return false; m_lastTextNodeEndedWithCollapsedSpace = true; // entire block is collapsed space return true; } - if (m_firstLetterText) - renderer = m_firstLetterText; - // Used when text boxes are out of order (Hebrew/Arabic w/ embeded LTR text) - if (renderer->containsReversedText()) { + auto& boxesRenderer = m_firstLetterText ? *m_firstLetterText : renderer; + if (boxesRenderer.containsReversedText()) { m_sortedTextBoxes.clear(); - for (InlineTextBox* textBox = renderer->firstTextBox(); textBox; textBox = textBox->nextTextBox()) { + for (InlineTextBox* textBox = boxesRenderer.firstTextBox(); textBox; textBox = textBox->nextTextBox()) m_sortedTextBoxes.append(textBox); - } - std::sort(m_sortedTextBoxes.begin(), m_sortedTextBoxes.end(), InlineTextBox::compareByStart); + std::sort(m_sortedTextBoxes.begin(), m_sortedTextBoxes.end(), InlineTextBox::compareByStart); m_sortedTextBoxesPosition = 0; - m_textBox = m_sortedTextBoxes.isEmpty() ? 0 : m_sortedTextBoxes[0]; + m_textBox = m_sortedTextBoxes.isEmpty() ? nullptr : m_sortedTextBoxes[0]; } handleTextBox(); @@ -597,58 +734,62 @@ bool TextIterator::handleTextNode() void TextIterator::handleTextBox() { - RenderText* renderer = m_firstLetterText ? m_firstLetterText : toRenderText(m_node->renderer()); - if (renderer->style().visibility() != VISIBLE && !m_ignoresStyleVisibility) { - m_textBox = 0; + Text& textNode = downcast<Text>(*m_node); + + auto& renderer = m_firstLetterText ? *m_firstLetterText : *textNode.renderer(); + if (renderer.style().visibility() != VISIBLE && !(m_behavior & TextIteratorIgnoresStyleVisibility)) { + m_textBox = nullptr; return; } - String str = renderer->text(); + String rendererText = renderer.text(); unsigned start = m_offset; - unsigned end = (m_node == m_endContainer) ? static_cast<unsigned>(m_endOffset) : UINT_MAX; + unsigned end = (&textNode == m_endContainer) ? static_cast<unsigned>(m_endOffset) : UINT_MAX; while (m_textBox) { unsigned textBoxStart = m_textBox->start(); unsigned runStart = std::max(textBoxStart, start); // Check for collapsed space at the start of this run. - InlineTextBox* firstTextBox = renderer->containsReversedText() ? (m_sortedTextBoxes.isEmpty() ? 0 : m_sortedTextBoxes[0]) : renderer->firstTextBox(); - bool needSpace = m_lastTextNodeEndedWithCollapsedSpace - || (m_textBox == firstTextBox && textBoxStart == runStart && runStart > 0); - if (needSpace && !isCollapsibleWhitespace(m_lastCharacter) && m_lastCharacter) { - if (m_lastTextNode == m_node && runStart > 0 && str[runStart - 1] == ' ') { + InlineTextBox* firstTextBox = renderer.containsReversedText() ? (m_sortedTextBoxes.isEmpty() ? nullptr : m_sortedTextBoxes[0]) : renderer.firstTextBox(); + bool needSpace = m_lastTextNodeEndedWithCollapsedSpace || (m_textBox == firstTextBox && textBoxStart == runStart && runStart); + if (needSpace && !renderer.style().isCollapsibleWhiteSpace(m_lastCharacter) && m_lastCharacter) { + if (m_lastTextNode == &textNode && runStart && rendererText[runStart - 1] == ' ') { unsigned spaceRunStart = runStart - 1; - while (spaceRunStart > 0 && str[spaceRunStart - 1] == ' ') + while (spaceRunStart && rendererText[spaceRunStart - 1] == ' ') --spaceRunStart; - emitText(m_node, renderer, spaceRunStart, spaceRunStart + 1); + emitText(textNode, renderer, spaceRunStart, spaceRunStart + 1); } else - emitCharacter(' ', m_node, 0, runStart, runStart); + emitCharacter(' ', textNode, nullptr, runStart, runStart); return; } unsigned textBoxEnd = textBoxStart + m_textBox->len(); unsigned runEnd = std::min(textBoxEnd, end); // Determine what the next text box will be, but don't advance yet - InlineTextBox* nextTextBox = 0; - if (renderer->containsReversedText()) { + InlineTextBox* nextTextBox = nullptr; + if (renderer.containsReversedText()) { if (m_sortedTextBoxesPosition + 1 < m_sortedTextBoxes.size()) nextTextBox = m_sortedTextBoxes[m_sortedTextBoxesPosition + 1]; } else nextTextBox = m_textBox->nextTextBox(); - ASSERT(!nextTextBox || &nextTextBox->renderer() == renderer); + ASSERT(!nextTextBox || &nextTextBox->renderer() == &renderer); if (runStart < runEnd) { // Handle either a single newline character (which becomes a space), // or a run of characters that does not include a newline. // This effectively translates newlines to spaces without copying the text. - if (str[runStart] == '\n') { - emitCharacter(' ', m_node, 0, runStart, runStart + 1); + if (rendererText[runStart] == '\n') { + emitCharacter(' ', textNode, nullptr, runStart, runStart + 1); m_offset = runStart + 1; } else { - size_t subrunEnd = str.find('\n', runStart); - if (subrunEnd == notFound || subrunEnd > runEnd) + size_t subrunEnd = rendererText.find('\n', runStart); + if (subrunEnd == notFound || subrunEnd > runEnd) { subrunEnd = runEnd; - + bool lastSpaceCollapsedByNextNonTextBox = !nextTextBox && (m_behavior & TextIteratorBehavesAsIfNodesFollowing) && rendererText.length() > runEnd; + if (lastSpaceCollapsedByNextNonTextBox) + ++subrunEnd; // runEnd stopped before last space. Increment by one to restore the space. + } m_offset = subrunEnd; - emitText(m_node, renderer, runStart, subrunEnd); + emitText(textNode, renderer, runStart, subrunEnd); } // If we are doing a subrun that doesn't go to the end of the text box, @@ -657,23 +798,23 @@ void TextIterator::handleTextBox() return; // Advance and return - unsigned nextRunStart = nextTextBox ? nextTextBox->start() : str.length(); + unsigned nextRunStart = nextTextBox ? nextTextBox->start() : rendererText.length(); if (nextRunStart > runEnd) m_lastTextNodeEndedWithCollapsedSpace = true; // collapsed space between runs or at the end m_textBox = nextTextBox; - if (renderer->containsReversedText()) + if (renderer.containsReversedText()) ++m_sortedTextBoxesPosition; return; } // Advance and continue m_textBox = nextTextBox; - if (renderer->containsReversedText()) + if (renderer.containsReversedText()) ++m_sortedTextBoxesPosition; } if (!m_textBox && m_remainingTextBox) { m_textBox = m_remainingTextBox; - m_remainingTextBox = 0; - m_firstLetterText = 0; + m_remainingTextBox = nullptr; + m_firstLetterText = nullptr; m_offset = 0; handleTextBox(); } @@ -688,12 +829,12 @@ static inline RenderText* firstRenderTextInFirstLetter(RenderBoxModelObject* fir return childrenOfType<RenderText>(*firstLetter).first(); } -void TextIterator::handleTextNodeFirstLetter(RenderTextFragment* renderer) +void TextIterator::handleTextNodeFirstLetter(RenderTextFragment& renderer) { - if (auto firstLetter = renderer->firstLetter()) { - if (firstLetter->style().visibility() != VISIBLE && !m_ignoresStyleVisibility) + if (auto* firstLetter = renderer.firstLetter()) { + if (firstLetter->style().visibility() != VISIBLE && !(m_behavior & TextIteratorIgnoresStyleVisibility)) return; - if (RenderText* firstLetterText = firstRenderTextInFirstLetter(firstLetter)) { + if (auto* firstLetterText = firstRenderTextInFirstLetter(firstLetter)) { m_handledFirstLetter = true; m_remainingTextBox = m_textBox; m_textBox = firstLetterText->firstTextBox(); @@ -709,19 +850,19 @@ bool TextIterator::handleReplacedElement() if (m_fullyClippedStack.top()) return false; - RenderObject* renderer = m_node->renderer(); - if (renderer->style().visibility() != VISIBLE && !m_ignoresStyleVisibility) + auto& renderer = *m_node->renderer(); + if (renderer.style().visibility() != VISIBLE && !(m_behavior & TextIteratorIgnoresStyleVisibility)) return false; if (m_lastTextNodeEndedWithCollapsedSpace) { - emitCharacter(' ', m_lastTextNode->parentNode(), m_lastTextNode, 1, 1); + emitCharacter(' ', *m_lastTextNode->parentNode(), m_lastTextNode, 1, 1); return false; } - if (m_entersTextControls && renderer->isTextControl()) { - if (TextControlInnerTextElement* innerTextElement = toRenderTextControl(renderer)->textFormControlElement().innerTextElement()) { + if ((m_behavior & TextIteratorEntersTextControls) && is<RenderTextControl>(renderer)) { + if (TextControlInnerTextElement* innerTextElement = downcast<RenderTextControl>(renderer).textFormControlElement().innerTextElement()) { m_node = innerTextElement->containingShadowRoot(); - pushFullyClippedState(m_fullyClippedStack, m_node); + pushFullyClippedState(m_fullyClippedStack, *m_node); m_offset = 0; return false; } @@ -729,19 +870,19 @@ bool TextIterator::handleReplacedElement() m_hasEmitted = true; - if (m_emitsObjectReplacementCharacters && renderer && renderer->isReplaced()) { - emitCharacter(objectReplacementCharacter, m_node->parentNode(), m_node, 0, 1); + if ((m_behavior & TextIteratorEmitsObjectReplacementCharacters) && renderer.isReplaced()) { + emitCharacter(objectReplacementCharacter, *m_node->parentNode(), m_node, 0, 1); // Don't process subtrees for embedded objects. If the text there is required, // it must be explicitly asked by specifying a range falling inside its boundaries. m_handledChildren = true; return true; } - if (m_emitsCharactersBetweenAllVisiblePositions) { + if (m_behavior & TextIteratorEmitsCharactersBetweenAllVisiblePositions) { // We want replaced elements to behave like punctuation for boundary // finding, and to simply take up space for the selection preservation // code in moveParagraphs, so we use a comma. - emitCharacter(',', m_node->parentNode(), m_node, 0, 1); + emitCharacter(',', *m_node->parentNode(), m_node, 0, 1); return true; } @@ -749,174 +890,169 @@ bool TextIterator::handleReplacedElement() m_positionOffsetBaseNode = m_node; m_positionStartOffset = 0; m_positionEndOffset = 1; - m_textCharacters = 0; - if (m_emitsImageAltText && renderer->isImage() && renderer->isRenderImage()) { - m_text = toRenderImage(renderer)->altText(); - if (!m_text.isEmpty()) { - m_textLength = m_text.length(); - m_lastCharacter = m_text[m_textLength - 1]; + if ((m_behavior & TextIteratorEmitsImageAltText) && is<RenderImage>(renderer)) { + String altText = downcast<RenderImage>(renderer).altText(); + if (unsigned length = altText.length()) { + m_lastCharacter = altText[length - 1]; + m_copyableText.set(WTFMove(altText)); + m_text = m_copyableText.text(); return true; } } - m_textLength = 0; + m_copyableText.reset(); + m_text = StringView(); m_lastCharacter = 0; - return true; } -bool TextIterator::hasVisibleTextNode(RenderText* renderer) -{ - if (renderer->style().visibility() == VISIBLE) - return true; - if (renderer->isTextFragment()) { - RenderTextFragment* fragment = static_cast<RenderTextFragment*>(renderer); - if (fragment->firstLetter() && fragment->firstLetter()->style().visibility() == VISIBLE) - return true; - } - return false; -} - -static bool shouldEmitTabBeforeNode(Node* node) +static bool shouldEmitTabBeforeNode(Node& node) { - RenderObject* r = node->renderer(); + auto* renderer = node.renderer(); // Table cells are delimited by tabs. - if (!r || !isTableCell(node)) + if (!renderer || !isTableCell(&node)) return false; - // Want a tab before every cell other than the first one - RenderTableCell* rc = toRenderTableCell(r); - RenderTable* t = rc->table(); - return t && (t->cellBefore(rc) || t->cellAbove(rc)); + // Want a tab before every cell other than the first one. + RenderTableCell& cell = downcast<RenderTableCell>(*renderer); + RenderTable* table = cell.table(); + return table && (table->cellBefore(&cell) || table->cellAbove(&cell)); } static bool shouldEmitNewlineForNode(Node* node, bool emitsOriginalText) { - RenderObject* renderer = node->renderer(); - - if (renderer ? !renderer->isBR() : !node->hasTagName(brTag)) + auto* renderer = node->renderer(); + if (!(renderer ? renderer->isBR() : node->hasTagName(brTag))) return false; - return emitsOriginalText || !(node->isInShadowTree() && node->shadowHost()->toInputElement()); + return emitsOriginalText || !(node->isInShadowTree() && is<HTMLInputElement>(*node->shadowHost())); +} + +static bool hasHeaderTag(HTMLElement& element) +{ + return element.hasTagName(h1Tag) + || element.hasTagName(h2Tag) + || element.hasTagName(h3Tag) + || element.hasTagName(h4Tag) + || element.hasTagName(h5Tag) + || element.hasTagName(h6Tag); } -static bool shouldEmitNewlinesBeforeAndAfterNode(Node* node) +static bool shouldEmitNewlinesBeforeAndAfterNode(Node& node) { // Block flow (versus inline flow) is represented by having // a newline both before and after the element. - RenderObject* r = node->renderer(); - if (!r) { - return (node->hasTagName(blockquoteTag) - || node->hasTagName(ddTag) - || node->hasTagName(divTag) - || node->hasTagName(dlTag) - || node->hasTagName(dtTag) - || node->hasTagName(h1Tag) - || node->hasTagName(h2Tag) - || node->hasTagName(h3Tag) - || node->hasTagName(h4Tag) - || node->hasTagName(h5Tag) - || node->hasTagName(h6Tag) - || node->hasTagName(hrTag) - || node->hasTagName(liTag) - || node->hasTagName(listingTag) - || node->hasTagName(olTag) - || node->hasTagName(pTag) - || node->hasTagName(preTag) - || node->hasTagName(trTag) - || node->hasTagName(ulTag)); + auto* renderer = node.renderer(); + if (!renderer) { + if (!is<HTMLElement>(node)) + return false; + auto& element = downcast<HTMLElement>(node); + return hasHeaderTag(element) + || element.hasTagName(blockquoteTag) + || element.hasTagName(ddTag) + || element.hasTagName(divTag) + || element.hasTagName(dlTag) + || element.hasTagName(dtTag) + || element.hasTagName(hrTag) + || element.hasTagName(liTag) + || element.hasTagName(listingTag) + || element.hasTagName(olTag) + || element.hasTagName(pTag) + || element.hasTagName(preTag) + || element.hasTagName(trTag) + || element.hasTagName(ulTag); } // Need to make an exception for table cells, because they are blocks, but we // want them tab-delimited rather than having newlines before and after. - if (isTableCell(node)) + if (isTableCell(&node)) return false; // Need to make an exception for table row elements, because they are neither // "inline" or "RenderBlock", but we want newlines for them. - if (r->isTableRow()) { - RenderTable* t = toRenderTableRow(r)->table(); - if (t && !t->isInline()) + if (is<RenderTableRow>(*renderer)) { + RenderTable* table = downcast<RenderTableRow>(*renderer).table(); + if (table && !table->isInline()) return true; } - return !r->isInline() && r->isRenderBlock() - && !r->isFloatingOrOutOfFlowPositioned() && !r->isBody() && !r->isRubyText(); + return !renderer->isInline() + && is<RenderBlock>(*renderer) + && !renderer->isFloatingOrOutOfFlowPositioned() + && !renderer->isBody() + && !renderer->isRubyText(); } -static bool shouldEmitNewlineAfterNode(Node* node) +static bool shouldEmitNewlineAfterNode(Node& node) { // FIXME: It should be better but slower to create a VisiblePosition here. if (!shouldEmitNewlinesBeforeAndAfterNode(node)) return false; // Check if this is the very last renderer in the document. // If so, then we should not emit a newline. - while ((node = NodeTraversal::nextSkippingChildren(node))) - if (node->renderer()) + Node* subsequentNode = &node; + while ((subsequentNode = NodeTraversal::nextSkippingChildren(*subsequentNode))) { + if (subsequentNode->renderer()) return true; + } return false; } -static bool shouldEmitNewlineBeforeNode(Node* node) +static bool shouldEmitNewlineBeforeNode(Node& node) { return shouldEmitNewlinesBeforeAndAfterNode(node); } -static bool shouldEmitExtraNewlineForNode(Node* node) +static bool shouldEmitExtraNewlineForNode(Node& node) { // When there is a significant collapsed bottom margin, emit an extra - // newline for a more realistic result. We end up getting the right + // newline for a more realistic result. We end up getting the right // result even without margin collapsing. For example: <div><p>text</p></div> // will work right even if both the <div> and the <p> have bottom margins. - RenderObject* r = node->renderer(); - if (!r || !r->isBox()) + + auto* renderer = node.renderer(); + if (!is<RenderBox>(renderer)) return false; - - // NOTE: We only do this for a select set of nodes, and fwiw WinIE appears - // not to do this at all - if (node->hasTagName(h1Tag) - || node->hasTagName(h2Tag) - || node->hasTagName(h3Tag) - || node->hasTagName(h4Tag) - || node->hasTagName(h5Tag) - || node->hasTagName(h6Tag) - || node->hasTagName(pTag)) { - int bottomMargin = toRenderBox(r)->collapsedMarginAfter(); - int fontSize = toRenderBox(r)->style().fontDescription().computedPixelSize(); - if (bottomMargin * 2 >= fontSize) - return true; - } - - return false; + + // NOTE: We only do this for a select set of nodes, and WinIE appears not to do this at all. + if (!is<HTMLElement>(node)) + return false; + + HTMLElement& element = downcast<HTMLElement>(node); + if (!hasHeaderTag(element) && !is<HTMLParagraphElement>(element)) + return false; + + int bottomMargin = downcast<RenderBox>(*renderer).collapsedMarginAfter(); + int fontSize = downcast<RenderBox>(*renderer).style().fontDescription().computedPixelSize(); + return bottomMargin * 2 >= fontSize; } -static int collapsedSpaceLength(RenderText* renderer, int textEnd) +static int collapsedSpaceLength(RenderText& renderer, int textEnd) { - const UChar* characters = renderer->text()->deprecatedCharacters(); - int length = renderer->text()->length(); - for (int i = textEnd; i < length; ++i) { - if (!renderer->style().isCollapsibleWhiteSpace(characters[i])) + StringImpl& text = *renderer.text(); + unsigned length = text.length(); + for (unsigned i = textEnd; i < length; ++i) { + if (!renderer.style().isCollapsibleWhiteSpace(text[i])) return i - textEnd; } - return length - textEnd; } -static int maxOffsetIncludingCollapsedSpaces(Node* node) +static int maxOffsetIncludingCollapsedSpaces(Node& node) { int offset = caretMaxOffset(node); - - if (node->renderer() && node->renderer()->isText()) - offset += collapsedSpaceLength(toRenderText(node->renderer()), offset); - + if (auto* renderer = node.renderer()) { + if (is<RenderText>(*renderer)) + offset += collapsedSpaceLength(downcast<RenderText>(*renderer), offset); + } return offset; } // Whether or not we should emit a character as we enter m_node (if it's a container) or as we hit it (if it's atomic). bool TextIterator::shouldRepresentNodeOffsetZero() { - if (m_emitsCharactersBetweenAllVisiblePositions && m_node->renderer() && m_node->renderer()->isTable()) + if ((m_behavior & TextIteratorEmitsCharactersBetweenAllVisiblePositions) && m_node->renderer() && m_node->renderer()->isTable()) return true; // Leave element positioned flush with start of a paragraph @@ -958,7 +1094,7 @@ bool TextIterator::shouldRepresentNodeOffsetZero() // Additionally, if the range we are iterating over contains huge sections of unrendered content, // we would create VisiblePositions on every call to this function without this check. if (!m_node->renderer() || m_node->renderer()->style().visibility() != VISIBLE - || (m_node->renderer()->isRenderBlockFlow() && !toRenderBlock(m_node->renderer())->height() && !m_node->hasTagName(bodyTag))) + || (is<RenderBlockFlow>(*m_node->renderer()) && !downcast<RenderBlockFlow>(*m_node->renderer()).height() && !is<HTMLBodyElement>(*m_node))) return false; // The startPos.isNotNull() check is needed because the start could be before the body, @@ -970,9 +1106,9 @@ bool TextIterator::shouldRepresentNodeOffsetZero() return startPos.isNotNull() && currPos.isNotNull() && !inSameLine(startPos, currPos); } -bool TextIterator::shouldEmitSpaceBeforeAndAfterNode(Node* node) +bool TextIterator::shouldEmitSpaceBeforeAndAfterNode(Node& node) { - return node->renderer() && node->renderer()->isTable() && (node->renderer()->isInline() || m_emitsCharactersBetweenAllVisiblePositions); + return node.renderer() && node.renderer()->isTable() && (node.renderer()->isInline() || (m_behavior & TextIteratorEmitsCharactersBetweenAllVisiblePositions)); } void TextIterator::representNodeOffsetZero() @@ -980,34 +1116,34 @@ void TextIterator::representNodeOffsetZero() // Emit a character to show the positioning of m_node. // When we haven't been emitting any characters, shouldRepresentNodeOffsetZero() can - // create VisiblePositions, which is expensive. So, we perform the inexpensive checks + // create VisiblePositions, which is expensive. So, we perform the inexpensive checks // on m_node to see if it necessitates emitting a character first and will early return // before encountering shouldRepresentNodeOffsetZero()s worse case behavior. - if (shouldEmitTabBeforeNode(m_node)) { + if (shouldEmitTabBeforeNode(*m_node)) { if (shouldRepresentNodeOffsetZero()) - emitCharacter('\t', m_node->parentNode(), m_node, 0, 0); - } else if (shouldEmitNewlineBeforeNode(m_node)) { + emitCharacter('\t', *m_node->parentNode(), m_node, 0, 0); + } else if (shouldEmitNewlineBeforeNode(*m_node)) { if (shouldRepresentNodeOffsetZero()) - emitCharacter('\n', m_node->parentNode(), m_node, 0, 0); - } else if (shouldEmitSpaceBeforeAndAfterNode(m_node)) { + emitCharacter('\n', *m_node->parentNode(), m_node, 0, 0); + } else if (shouldEmitSpaceBeforeAndAfterNode(*m_node)) { if (shouldRepresentNodeOffsetZero()) - emitCharacter(' ', m_node->parentNode(), m_node, 0, 0); + emitCharacter(' ', *m_node->parentNode(), m_node, 0, 0); } } bool TextIterator::handleNonTextNode() { - if (shouldEmitNewlineForNode(m_node, m_emitsOriginalText)) - emitCharacter('\n', m_node->parentNode(), m_node, 0, 1); - else if (m_emitsCharactersBetweenAllVisiblePositions && m_node->renderer() && m_node->renderer()->isHR()) - emitCharacter(' ', m_node->parentNode(), m_node, 0, 1); + if (shouldEmitNewlineForNode(m_node, m_behavior & TextIteratorEmitsOriginalText)) + emitCharacter('\n', *m_node->parentNode(), m_node, 0, 1); + else if ((m_behavior & TextIteratorEmitsCharactersBetweenAllVisiblePositions) && m_node->renderer() && m_node->renderer()->isHR()) + emitCharacter(' ', *m_node->parentNode(), m_node, 0, 1); else representNodeOffsetZero(); return true; } -void TextIterator::exitNode() +void TextIterator::exitNode(Node* exitedNode) { // prevent emitting a newline when exiting a collapsed block at beginning of the range // FIXME: !m_hasEmitted does not necessarily mean there was a collapsed block... it could @@ -1019,212 +1155,160 @@ void TextIterator::exitNode() // Emit with a position *inside* m_node, after m_node's contents, in // case it is a block, because the run should start where the // emitted character is positioned visually. - Node* baseNode = m_node->lastChild() ? m_node->lastChild() : m_node; + Node* baseNode = exitedNode; // FIXME: This shouldn't require the m_lastTextNode to be true, but we can't change that without making - // the logic in _web_attributedStringFromRange match. We'll get that for free when we switch to use + // the logic in _web_attributedStringFromRange match. We'll get that for free when we switch to use // TextIterator in _web_attributedStringFromRange. // See <rdar://problem/5428427> for an example of how this mismatch will cause problems. - if (m_lastTextNode && shouldEmitNewlineAfterNode(m_node)) { + if (m_lastTextNode && shouldEmitNewlineAfterNode(*m_node)) { // use extra newline to represent margin bottom, as needed - bool addNewline = shouldEmitExtraNewlineForNode(m_node); + bool addNewline = shouldEmitExtraNewlineForNode(*m_node); // FIXME: We need to emit a '\n' as we leave an empty block(s) that // contain a VisiblePosition when doing selection preservation. if (m_lastCharacter != '\n') { // insert a newline with a position following this block's contents. - emitCharacter('\n', baseNode->parentNode(), baseNode, 1, 1); + emitCharacter('\n', *baseNode->parentNode(), baseNode, 1, 1); // remember whether to later add a newline for the current node - ASSERT(!m_needsAnotherNewline); - m_needsAnotherNewline = addNewline; + ASSERT(!m_nodeForAdditionalNewline); + if (addNewline) + m_nodeForAdditionalNewline = baseNode; } else if (addNewline) // insert a newline with a position following this block's contents. - emitCharacter('\n', baseNode->parentNode(), baseNode, 1, 1); + emitCharacter('\n', *baseNode->parentNode(), baseNode, 1, 1); } // If nothing was emitted, see if we need to emit a space. - if (!m_positionNode && shouldEmitSpaceBeforeAndAfterNode(m_node)) - emitCharacter(' ', baseNode->parentNode(), baseNode, 1, 1); + if (!m_positionNode && shouldEmitSpaceBeforeAndAfterNode(*m_node)) + emitCharacter(' ', *baseNode->parentNode(), baseNode, 1, 1); } -void TextIterator::emitCharacter(UChar c, Node* textNode, Node* offsetBaseNode, int textStartOffset, int textEndOffset) +void TextIterator::emitCharacter(UChar character, Node& characterNode, Node* offsetBaseNode, int textStartOffset, int textEndOffset) { m_hasEmitted = true; // remember information with which to construct the TextIterator::range() - // NOTE: textNode is often not a text node, so the range will specify child nodes of positionNode - m_positionNode = textNode; + m_positionNode = &characterNode; m_positionOffsetBaseNode = offsetBaseNode; m_positionStartOffset = textStartOffset; m_positionEndOffset = textEndOffset; - - // remember information with which to construct the TextIterator::characters() and length() - m_singleCharacterBuffer = c; - m_textCharacters = &m_singleCharacterBuffer; - m_textLength = 1; - // remember some iteration state + m_copyableText.set(character); + m_text = m_copyableText.text(); + m_lastCharacter = character; m_lastTextNodeEndedWithCollapsedSpace = false; - m_lastCharacter = c; + m_nextRunNeedsWhitespace = false; } -void TextIterator::emitText(Node* textNode, RenderObject* renderObject, int textStartOffset, int textEndOffset) +void TextIterator::emitText(Text& textNode, RenderText& renderer, int textStartOffset, int textEndOffset) { - RenderText* renderer = toRenderText(renderObject); - m_text = m_emitsOriginalText ? renderer->originalText() : (m_emitsTextWithoutTranscoding ? renderer->textWithoutConvertingBackslashToYenSymbol() : renderer->text()); - ASSERT(!m_text.isEmpty()); - ASSERT(0 <= textStartOffset && textStartOffset < static_cast<int>(m_text.length())); - ASSERT(0 <= textEndOffset && textEndOffset <= static_cast<int>(m_text.length())); + ASSERT(textStartOffset >= 0); + ASSERT(textEndOffset >= 0); ASSERT(textStartOffset <= textEndOffset); - m_positionNode = textNode; - m_positionOffsetBaseNode = 0; + // FIXME: This probably yields the wrong offsets when text-transform: lowercase turns a single character into two characters. + String string = (m_behavior & TextIteratorEmitsOriginalText) ? renderer.originalText() + : ((m_behavior & TextIteratorEmitsTextsWithoutTranscoding) ? renderer.textWithoutConvertingBackslashToYenSymbol() : renderer.text()); + + ASSERT(string.length() >= static_cast<unsigned>(textEndOffset)); + + m_positionNode = &textNode; + m_positionOffsetBaseNode = nullptr; m_positionStartOffset = textStartOffset; m_positionEndOffset = textEndOffset; - m_textCharacters = 0; - m_textLength = textEndOffset - textStartOffset; - m_lastCharacter = m_text[textEndOffset - 1]; + + m_lastCharacter = string[textEndOffset - 1]; + m_copyableText.set(WTFMove(string), textStartOffset, textEndOffset - textStartOffset); + m_text = m_copyableText.text(); m_lastTextNodeEndedWithCollapsedSpace = false; + m_nextRunNeedsWhitespace = false; m_hasEmitted = true; } -void TextIterator::emitText(Node* textNode, int textStartOffset, int textEndOffset) +Ref<Range> TextIterator::range() const { - emitText(textNode, m_node->renderer(), textStartOffset, textEndOffset); -} + ASSERT(!atEnd()); -PassRefPtr<Range> TextIterator::range() const -{ // use the current run information, if we have it - if (m_positionNode) { - if (m_positionOffsetBaseNode) { - int index = m_positionOffsetBaseNode->nodeIndex(); - m_positionStartOffset += index; - m_positionEndOffset += index; - m_positionOffsetBaseNode = 0; - } - return Range::create(m_positionNode->document(), m_positionNode, m_positionStartOffset, m_positionNode, m_positionEndOffset); + if (m_positionOffsetBaseNode) { + unsigned index = m_positionOffsetBaseNode->computeNodeIndex(); + m_positionStartOffset += index; + m_positionEndOffset += index; + m_positionOffsetBaseNode = nullptr; } - - // otherwise, return the end of the overall range we were given - if (m_endContainer) - return Range::create(m_endContainer->document(), m_endContainer, m_endOffset, m_endContainer, m_endOffset); - - return 0; + return Range::create(m_positionNode->document(), m_positionNode, m_positionStartOffset, m_positionNode, m_positionEndOffset); } Node* TextIterator::node() const { - RefPtr<Range> textRange = range(); - if (!textRange) - return 0; + Ref<Range> textRange = range(); - Node* node = textRange->startContainer(); - if (!node) - return 0; - if (node->offsetInCharacters()) - return node; + Node& node = textRange->startContainer(); + if (node.offsetInCharacters()) + return &node; - return node->childNode(textRange->startOffset()); + return node.traverseToChildAt(textRange->startOffset()); } // -------- -SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range* r, TextIteratorBehavior behavior) - : m_node(0) - , m_offset(0) - , m_handledNode(false) - , m_handledChildren(false) - , m_startNode(0) - , m_startOffset(0) - , m_endNode(0) - , m_endOffset(0) - , m_positionNode(0) - , m_positionStartOffset(0) - , m_positionEndOffset(0) - , m_textCharacters(0) - , m_textLength(0) - , m_lastTextNode(0) - , m_lastCharacter(0) - , m_singleCharacterBuffer(0) - , m_havePassedStartNode(false) - , m_shouldHandleFirstLetter(false) - , m_stopsOnFormControls(behavior & TextIteratorStopsOnFormControls) - , m_shouldStop(false) - , m_emitsOriginalText(false) -{ - ASSERT(behavior == TextIteratorDefaultBehavior || behavior == TextIteratorStopsOnFormControls); - - if (!r) - return; - - r->ownerDocument().updateLayoutIgnorePendingStylesheets(); +SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range& range) +{ + range.ownerDocument().updateLayoutIgnorePendingStylesheets(); - Node* startNode = r->startContainer(); - if (!startNode) - return; - Node* endNode = r->endContainer(); - int startOffset = r->startOffset(); - int endOffset = r->endOffset(); + Node* startNode = &range.startContainer(); + Node* endNode = &range.endContainer(); + int startOffset = range.startOffset(); + int endOffset = range.endOffset(); if (!startNode->offsetInCharacters()) { - if (startOffset >= 0 && startOffset < static_cast<int>(startNode->childNodeCount())) { - startNode = startNode->childNode(startOffset); + if (startOffset >= 0 && startOffset < static_cast<int>(startNode->countChildNodes())) { + startNode = startNode->traverseToChildAt(startOffset); startOffset = 0; } } if (!endNode->offsetInCharacters()) { - if (endOffset > 0 && endOffset <= static_cast<int>(endNode->childNodeCount())) { - endNode = endNode->childNode(endOffset - 1); + if (endOffset > 0 && endOffset <= static_cast<int>(endNode->countChildNodes())) { + endNode = endNode->traverseToChildAt(endOffset - 1); endOffset = lastOffsetInNode(endNode); } } m_node = endNode; - setUpFullyClippedStack(m_fullyClippedStack, m_node); + setUpFullyClippedStack(m_fullyClippedStack, *m_node); m_offset = endOffset; m_handledNode = false; m_handledChildren = endOffset == 0; - m_startNode = startNode; + m_startContainer = startNode; m_startOffset = startOffset; - m_endNode = endNode; + m_endContainer = endNode; m_endOffset = endOffset; -#ifndef NDEBUG - // Need this just because of the assert. m_positionNode = endNode; -#endif - m_lastTextNode = 0; + m_lastTextNode = nullptr; m_lastCharacter = '\n'; - m_havePassedStartNode = false; + m_havePassedStartContainer = false; advance(); } void SimplifiedBackwardsTextIterator::advance() { - ASSERT(m_positionNode); + ASSERT(!atEnd()); - if (m_shouldStop) - return; + m_positionNode = nullptr; + m_copyableText.reset(); + m_text = StringView(); - if (m_stopsOnFormControls && HTMLFormControlElement::enclosingFormControlElement(m_node)) { - m_shouldStop = true; - return; - } - - m_positionNode = 0; - m_textLength = 0; - - while (m_node && !m_havePassedStartNode) { + while (m_node && !m_havePassedStartContainer) { // Don't handle node if we start iterating at [node, 0]. - if (!m_handledNode && !(m_node == m_endNode && m_endOffset == 0)) { - RenderObject* renderer = m_node->renderer(); - if (renderer && renderer->isText() && m_node->nodeType() == Node::TEXT_NODE) { - // FIXME: What about CDATA_SECTION_NODE? + if (!m_handledNode && !(m_node == m_endContainer && !m_endOffset)) { + auto* renderer = m_node->renderer(); + if (renderer && renderer->isText() && m_node->isTextNode()) { if (renderer->style().visibility() == VISIBLE && m_offset > 0) m_handledNode = handleTextNode(); } else if (renderer && (renderer->isImage() || renderer->isWidget())) { @@ -1238,14 +1322,11 @@ void SimplifiedBackwardsTextIterator::advance() if (!m_handledChildren && m_node->hasChildNodes()) { m_node = m_node->lastChild(); - pushFullyClippedState(m_fullyClippedStack, m_node); + pushFullyClippedState(m_fullyClippedStack, *m_node); } else { // Exit empty containers as we pass over them or containers // where [container, 0] is where we started iterating. - if (!m_handledNode - && canHaveChildrenForEditing(m_node) - && m_node->parentNode() - && (!m_node->lastChild() || (m_node == m_endNode && !m_endOffset))) { + if (!m_handledNode && canHaveChildrenForEditing(*m_node) && m_node->parentNode() && (!m_node->lastChild() || (m_node == m_endContainer && !m_endOffset))) { exitNode(); if (m_positionNode) { m_handledNode = true; @@ -1269,14 +1350,14 @@ void SimplifiedBackwardsTextIterator::advance() m_fullyClippedStack.pop(); if (advanceRespectingRange(m_node->previousSibling())) - pushFullyClippedState(m_fullyClippedStack, m_node); + pushFullyClippedState(m_fullyClippedStack, *m_node); else - m_node = 0; + m_node = nullptr; } // For the purpose of word boundary detection, // we should iterate all visible text and trailing (collapsed) whitespaces. - m_offset = m_node ? maxOffsetIncludingCollapsedSpaces(m_node) : 0; + m_offset = m_node ? maxOffsetIncludingCollapsedSpaces(*m_node) : 0; m_handledNode = false; m_handledChildren = false; @@ -1287,7 +1368,9 @@ void SimplifiedBackwardsTextIterator::advance() bool SimplifiedBackwardsTextIterator::handleTextNode() { - m_lastTextNode = m_node; + Text& textNode = downcast<Text>(*m_node); + + m_lastTextNode = &textNode; int startOffset; int offsetInNode; @@ -1296,98 +1379,105 @@ bool SimplifiedBackwardsTextIterator::handleTextNode() return true; String text = renderer->text(); - if (!renderer->firstTextBox() && text.length() > 0) + if (!renderer->hasRenderedText() && text.length()) + return true; + + if (startOffset + offsetInNode == m_offset) { + ASSERT(!m_shouldHandleFirstLetter); return true; + } m_positionEndOffset = m_offset; m_offset = startOffset + offsetInNode; m_positionNode = m_node; m_positionStartOffset = m_offset; - ASSERT(0 <= m_positionStartOffset - offsetInNode && m_positionStartOffset - offsetInNode <= static_cast<int>(text.length())); - ASSERT(1 <= m_positionEndOffset - offsetInNode && m_positionEndOffset - offsetInNode <= static_cast<int>(text.length())); - ASSERT(m_positionStartOffset <= m_positionEndOffset); - - m_textLength = m_positionEndOffset - m_positionStartOffset; - m_textCharacters = text.deprecatedCharacters() + (m_positionStartOffset - offsetInNode); - ASSERT(m_textCharacters >= text.deprecatedCharacters()); - ASSERT(m_textCharacters + m_textLength <= text.deprecatedCharacters() + static_cast<int>(text.length())); + ASSERT(m_positionStartOffset < m_positionEndOffset); + ASSERT(m_positionStartOffset - offsetInNode >= 0); + ASSERT(m_positionEndOffset - offsetInNode > 0); + ASSERT(m_positionEndOffset - offsetInNode <= static_cast<int>(text.length())); - m_lastCharacter = text[m_positionEndOffset - 1]; + m_lastCharacter = text[m_positionEndOffset - offsetInNode - 1]; + m_copyableText.set(WTFMove(text), m_positionStartOffset - offsetInNode, m_positionEndOffset - m_positionStartOffset); + m_text = m_copyableText.text(); return !m_shouldHandleFirstLetter; } RenderText* SimplifiedBackwardsTextIterator::handleFirstLetter(int& startOffset, int& offsetInNode) { - RenderText* renderer = toRenderText(m_node->renderer()); - startOffset = (m_node == m_startNode) ? m_startOffset : 0; + RenderText& renderer = downcast<RenderText>(*m_node->renderer()); + startOffset = (m_node == m_startContainer) ? m_startOffset : 0; - if (!renderer->isTextFragment()) { + if (!is<RenderTextFragment>(renderer)) { offsetInNode = 0; - return renderer; + return &renderer; } - RenderTextFragment* fragment = toRenderTextFragment(renderer); - int offsetAfterFirstLetter = fragment->start(); + RenderTextFragment& fragment = downcast<RenderTextFragment>(renderer); + int offsetAfterFirstLetter = fragment.start(); if (startOffset >= offsetAfterFirstLetter) { ASSERT(!m_shouldHandleFirstLetter); offsetInNode = offsetAfterFirstLetter; - return renderer; + return &renderer; } - if (!m_shouldHandleFirstLetter && offsetAfterFirstLetter < m_offset) { + if (!m_shouldHandleFirstLetter && startOffset + offsetAfterFirstLetter < m_offset) { m_shouldHandleFirstLetter = true; offsetInNode = offsetAfterFirstLetter; - return renderer; + return &renderer; } m_shouldHandleFirstLetter = false; offsetInNode = 0; - return firstRenderTextInFirstLetter(fragment->firstLetter()); + RenderText* firstLetterRenderer = firstRenderTextInFirstLetter(fragment.firstLetter()); + + m_offset = firstLetterRenderer->caretMaxOffset(); + m_offset += collapsedSpaceLength(*firstLetterRenderer, m_offset); + + return firstLetterRenderer; } bool SimplifiedBackwardsTextIterator::handleReplacedElement() { - unsigned index = m_node->nodeIndex(); + unsigned index = m_node->computeNodeIndex(); // We want replaced elements to behave like punctuation for boundary // finding, and to simply take up space for the selection preservation - // code in moveParagraphs, so we use a comma. Unconditionally emit + // code in moveParagraphs, so we use a comma. Unconditionally emit // here because this iterator is only used for boundary finding. - emitCharacter(',', m_node->parentNode(), index, index + 1); + emitCharacter(',', *m_node->parentNode(), index, index + 1); return true; } bool SimplifiedBackwardsTextIterator::handleNonTextNode() { // We can use a linefeed in place of a tab because this simple iterator is only used to - // find boundaries, not actual content. A linefeed breaks words, sentences, and paragraphs. - if (shouldEmitNewlineForNode(m_node, m_emitsOriginalText) || shouldEmitNewlineAfterNode(m_node) || shouldEmitTabBeforeNode(m_node)) { - unsigned index = m_node->nodeIndex(); + // find boundaries, not actual content. A linefeed breaks words, sentences, and paragraphs. + if (shouldEmitNewlineForNode(m_node, m_behavior & TextIteratorEmitsOriginalText) || shouldEmitNewlineAfterNode(*m_node) || shouldEmitTabBeforeNode(*m_node)) { + unsigned index = m_node->computeNodeIndex(); // The start of this emitted range is wrong. Ensuring correctness would require // VisiblePositions and so would be slow. previousBoundary expects this. - emitCharacter('\n', m_node->parentNode(), index + 1, index + 1); + emitCharacter('\n', *m_node->parentNode(), index + 1, index + 1); } return true; } void SimplifiedBackwardsTextIterator::exitNode() { - if (shouldEmitNewlineForNode(m_node, m_emitsOriginalText) || shouldEmitNewlineBeforeNode(m_node) || shouldEmitTabBeforeNode(m_node)) { + if (shouldEmitNewlineForNode(m_node, m_behavior & TextIteratorEmitsOriginalText) || shouldEmitNewlineBeforeNode(*m_node) || shouldEmitTabBeforeNode(*m_node)) { // The start of this emitted range is wrong. Ensuring correctness would require // VisiblePositions and so would be slow. previousBoundary expects this. - emitCharacter('\n', m_node, 0, 0); + emitCharacter('\n', *m_node, 0, 0); } } -void SimplifiedBackwardsTextIterator::emitCharacter(UChar c, Node* node, int startOffset, int endOffset) +void SimplifiedBackwardsTextIterator::emitCharacter(UChar c, Node& node, int startOffset, int endOffset) { - m_singleCharacterBuffer = c; - m_positionNode = node; + m_positionNode = &node; m_positionStartOffset = startOffset; m_positionEndOffset = endOffset; - m_textCharacters = &m_singleCharacterBuffer; - m_textLength = 1; + m_copyableText.set(c); + m_text = m_copyableText.text(); m_lastCharacter = c; } @@ -1395,48 +1485,47 @@ bool SimplifiedBackwardsTextIterator::advanceRespectingRange(Node* next) { if (!next) return false; - m_havePassedStartNode |= m_node == m_startNode; - if (m_havePassedStartNode) + m_havePassedStartContainer |= m_node == m_startContainer; + if (m_havePassedStartContainer) return false; m_node = next; return true; } -PassRefPtr<Range> SimplifiedBackwardsTextIterator::range() const +Ref<Range> SimplifiedBackwardsTextIterator::range() const { - if (m_positionNode) - return Range::create(m_positionNode->document(), m_positionNode, m_positionStartOffset, m_positionNode, m_positionEndOffset); - - return Range::create(m_startNode->document(), m_startNode, m_startOffset, m_startNode, m_startOffset); + ASSERT(!atEnd()); + + return Range::create(m_positionNode->document(), m_positionNode, m_positionStartOffset, m_positionNode, m_positionEndOffset); } // -------- -CharacterIterator::CharacterIterator(const Range* r, TextIteratorBehavior behavior) - : m_offset(0) +CharacterIterator::CharacterIterator(const Range& range, TextIteratorBehavior behavior) + : m_underlyingIterator(&range, behavior) + , m_offset(0) , m_runOffset(0) , m_atBreak(true) - , m_textIterator(r, behavior) { - while (!atEnd() && m_textIterator.length() == 0) - m_textIterator.advance(); + while (!atEnd() && !m_underlyingIterator.text().length()) + m_underlyingIterator.advance(); } -PassRefPtr<Range> CharacterIterator::range() const +Ref<Range> CharacterIterator::range() const { - RefPtr<Range> r = m_textIterator.range(); - if (!m_textIterator.atEnd()) { - if (m_textIterator.length() <= 1) { + Ref<Range> range = m_underlyingIterator.range(); + if (!m_underlyingIterator.atEnd()) { + if (m_underlyingIterator.text().length() <= 1) { ASSERT(m_runOffset == 0); } else { - Node* n = r->startContainer(); - ASSERT(n == r->endContainer()); - int offset = r->startOffset() + m_runOffset; - r->setStart(n, offset, ASSERT_NO_EXCEPTION); - r->setEnd(n, offset + 1, ASSERT_NO_EXCEPTION); + Node& node = range->startContainer(); + ASSERT(&node == &range->endContainer()); + int offset = range->startOffset() + m_runOffset; + range->setStart(node, offset); + range->setEnd(node, offset + 1); } } - return r.release(); + return range; } void CharacterIterator::advance(int count) @@ -1448,94 +1537,85 @@ void CharacterIterator::advance(int count) m_atBreak = false; - // easy if there is enough left in the current m_textIterator run - int remaining = m_textIterator.length() - m_runOffset; + // easy if there is enough left in the current m_underlyingIterator run + int remaining = m_underlyingIterator.text().length() - m_runOffset; if (count < remaining) { m_runOffset += count; m_offset += count; return; } - // exhaust the current m_textIterator run + // exhaust the current m_underlyingIterator run count -= remaining; m_offset += remaining; - // move to a subsequent m_textIterator run - for (m_textIterator.advance(); !atEnd(); m_textIterator.advance()) { - int runLength = m_textIterator.length(); - if (runLength == 0) + // move to a subsequent m_underlyingIterator run + for (m_underlyingIterator.advance(); !atEnd(); m_underlyingIterator.advance()) { + int runLength = m_underlyingIterator.text().length(); + if (!runLength) m_atBreak = true; else { - // see whether this is m_textIterator to use + // see whether this is m_underlyingIterator to use if (count < runLength) { m_runOffset = count; m_offset += count; return; } - // exhaust this m_textIterator run + // exhaust this m_underlyingIterator run count -= runLength; m_offset += runLength; } } - // ran to the end of the m_textIterator... no more runs left + // ran to the end of the m_underlyingIterator... no more runs left m_atBreak = true; m_runOffset = 0; } -String CharacterIterator::string(int numChars) -{ - Vector<UChar> result; - result.reserveInitialCapacity(numChars); - while (numChars > 0 && !atEnd()) { - int runSize = std::min(numChars, length()); - result.append(characters(), runSize); - numChars -= runSize; - advance(runSize); - } - return String::adopt(result); -} - -static PassRefPtr<Range> characterSubrange(CharacterIterator& it, int offset, int length) +static Ref<Range> characterSubrange(Document& document, CharacterIterator& it, int offset, int length) { it.advance(offset); - RefPtr<Range> start = it.range(); + if (it.atEnd()) + return Range::create(document); + + Ref<Range> start = it.range(); if (length > 1) it.advance(length - 1); - RefPtr<Range> end = it.range(); + if (it.atEnd()) + return Range::create(document); - return Range::create(start->startContainer()->document(), - start->startContainer(), start->startOffset(), - end->endContainer(), end->endOffset()); + Ref<Range> end = it.range(); + + return Range::create(document, &start->startContainer(), start->startOffset(), &end->endContainer(), end->endOffset()); } -BackwardsCharacterIterator::BackwardsCharacterIterator(const Range* range, TextIteratorBehavior behavior) - : m_offset(0) +BackwardsCharacterIterator::BackwardsCharacterIterator(const Range& range) + : m_underlyingIterator(range) + , m_offset(0) , m_runOffset(0) , m_atBreak(true) - , m_textIterator(range, behavior) { - while (!atEnd() && !m_textIterator.length()) - m_textIterator.advance(); + while (!atEnd() && !m_underlyingIterator.text().length()) + m_underlyingIterator.advance(); } -PassRefPtr<Range> BackwardsCharacterIterator::range() const +Ref<Range> BackwardsCharacterIterator::range() const { - RefPtr<Range> r = m_textIterator.range(); - if (!m_textIterator.atEnd()) { - if (m_textIterator.length() <= 1) + Ref<Range> r = m_underlyingIterator.range(); + if (!m_underlyingIterator.atEnd()) { + if (m_underlyingIterator.text().length() <= 1) ASSERT(m_runOffset == 0); else { - Node* n = r->startContainer(); - ASSERT(n == r->endContainer()); + Node& node = r->startContainer(); + ASSERT(&node == &r->endContainer()); int offset = r->endOffset() - m_runOffset; - r->setStart(n, offset - 1, ASSERT_NO_EXCEPTION); - r->setEnd(n, offset, ASSERT_NO_EXCEPTION); + r->setStart(node, offset - 1); + r->setEnd(node, offset); } } - return r.release(); + return r; } void BackwardsCharacterIterator::advance(int count) @@ -1547,7 +1627,7 @@ void BackwardsCharacterIterator::advance(int count) m_atBreak = false; - int remaining = m_textIterator.length() - m_runOffset; + int remaining = m_underlyingIterator.text().length() - m_runOffset; if (count < remaining) { m_runOffset += count; m_offset += count; @@ -1557,8 +1637,8 @@ void BackwardsCharacterIterator::advance(int count) count -= remaining; m_offset += remaining; - for (m_textIterator.advance(); !atEnd(); m_textIterator.advance()) { - int runLength = m_textIterator.length(); + for (m_underlyingIterator.advance(); !atEnd(); m_underlyingIterator.advance()) { + int runLength = m_underlyingIterator.text().length(); if (runLength == 0) m_atBreak = true; else { @@ -1579,18 +1659,13 @@ void BackwardsCharacterIterator::advance(int count) // -------- -WordAwareIterator::WordAwareIterator(const Range* r) - : m_previousText(0) +WordAwareIterator::WordAwareIterator(const Range& range) + : m_underlyingIterator(&range) , m_didLookAhead(true) // so we consider the first chunk from the text iterator - , m_textIterator(r) { advance(); // get in position over the first chunk of text } -WordAwareIterator::~WordAwareIterator() -{ -} - // We're always in one of these modes: // - The current chunk in the text iterator is our current chunk // (typically its a piece of whitespace, or text that ended with whitespace) @@ -1602,74 +1677,59 @@ WordAwareIterator::~WordAwareIterator() void WordAwareIterator::advance() { - m_previousText = 0; - m_buffer.clear(); // toss any old buffer we built up + m_previousText.reset(); + m_buffer.clear(); // If last time we did a look-ahead, start with that looked-ahead chunk now if (!m_didLookAhead) { - ASSERT(!m_textIterator.atEnd()); - m_textIterator.advance(); + ASSERT(!m_underlyingIterator.atEnd()); + m_underlyingIterator.advance(); } m_didLookAhead = false; // Go to next non-empty chunk - while (!m_textIterator.atEnd() && m_textIterator.length() == 0) - m_textIterator.advance(); - m_range = m_textIterator.range(); - - if (m_textIterator.atEnd()) + while (!m_underlyingIterator.atEnd() && !m_underlyingIterator.text().length()) + m_underlyingIterator.advance(); + if (m_underlyingIterator.atEnd()) return; - + while (1) { // If this chunk ends in whitespace we can just use it as our chunk. - if (isSpaceOrNewline(m_textIterator.characters()[m_textIterator.length() - 1])) + if (isSpaceOrNewline(m_underlyingIterator.text()[m_underlyingIterator.text().length() - 1])) return; // If this is the first chunk that failed, save it in previousText before look ahead - if (m_buffer.isEmpty()) { - m_previousText = m_textIterator.characters(); - m_previousLength = m_textIterator.length(); - } + if (m_buffer.isEmpty()) + m_previousText = m_underlyingIterator.copyableText(); - // Look ahead to next chunk. If it is whitespace or a break, we can use the previous stuff - m_textIterator.advance(); - if (m_textIterator.atEnd() || m_textIterator.length() == 0 || isSpaceOrNewline(m_textIterator.characters()[0])) { + // Look ahead to next chunk. If it is whitespace or a break, we can use the previous stuff + m_underlyingIterator.advance(); + if (m_underlyingIterator.atEnd() || !m_underlyingIterator.text().length() || isSpaceOrNewline(m_underlyingIterator.text()[0])) { m_didLookAhead = true; return; } if (m_buffer.isEmpty()) { // Start gobbling chunks until we get to a suitable stopping point - m_buffer.append(m_previousText, m_previousLength); - m_previousText = 0; + append(m_buffer, m_previousText.text()); + m_previousText.reset(); } - m_buffer.append(m_textIterator.characters(), m_textIterator.length()); - int exception = 0; - m_range->setEnd(m_textIterator.range()->endContainer(), m_textIterator.range()->endOffset(), exception); + append(m_buffer, m_underlyingIterator.text()); } } -int WordAwareIterator::length() const +StringView WordAwareIterator::text() const { if (!m_buffer.isEmpty()) - return m_buffer.size(); - if (m_previousText) - return m_previousLength; - return m_textIterator.length(); -} - -const UChar* WordAwareIterator::characters() const -{ - if (!m_buffer.isEmpty()) - return m_buffer.data(); - if (m_previousText) - return m_previousText; - return m_textIterator.characters(); + return StringView(m_buffer.data(), m_buffer.size()); + if (m_previousText.text().length()) + return m_previousText.text(); + return m_underlyingIterator.text(); } // -------- -static inline UChar foldQuoteMarkOrSoftHyphen(UChar c) +static inline UChar foldQuoteMark(UChar c) { switch (c) { case hebrewPunctuationGershayim: @@ -1680,37 +1740,29 @@ static inline UChar foldQuoteMarkOrSoftHyphen(UChar c) case leftSingleQuotationMark: case rightSingleQuotationMark: return '\''; - case softHyphen: - // Replace soft hyphen with an ignorable character so that their presence or absence will - // not affect string comparison. - return 0; default: return c; } } -static inline void foldQuoteMarksAndSoftHyphens(String& s) +// FIXME: We'd like to tailor the searcher to fold quote marks for us instead +// of doing it in a separate replacement pass here, but ICU doesn't offer a way +// to add tailoring on top of the locale-specific tailoring as of this writing. +static inline String foldQuoteMarks(String string) { - s.replace(hebrewPunctuationGeresh, '\''); - s.replace(hebrewPunctuationGershayim, '"'); - s.replace(leftDoubleQuotationMark, '"'); - s.replace(leftSingleQuotationMark, '\''); - s.replace(rightDoubleQuotationMark, '"'); - s.replace(rightSingleQuotationMark, '\''); - // Replace soft hyphen with an ignorable character so that their presence or absence will - // not affect string comparison. - s.replace(softHyphen, 0); + string.replace(hebrewPunctuationGeresh, '\''); + string.replace(hebrewPunctuationGershayim, '"'); + string.replace(leftDoubleQuotationMark, '"'); + string.replace(leftSingleQuotationMark, '\''); + string.replace(rightDoubleQuotationMark, '"'); + string.replace(rightSingleQuotationMark, '\''); + + return string; } #if !UCONFIG_NO_COLLATION -static inline void foldQuoteMarksAndSoftHyphens(UChar* data, size_t length) -{ - for (size_t i = 0; i < length; ++i) - data[i] = foldQuoteMarkOrSoftHyphen(data[i]); -} - -static const size_t minimumSearchBufferSize = 8192; +const size_t minimumSearchBufferSize = 8192; #ifndef NDEBUG static bool searcherInUse; @@ -1922,7 +1974,9 @@ static inline bool isCombiningVoicedSoundMark(UChar character) static inline bool containsKanaLetters(const String& pattern) { - const UChar* characters = pattern.deprecatedCharacters(); + if (pattern.is8Bit()) + return false; + const UChar* characters = pattern.characters16(); unsigned length = pattern.length(); for (unsigned i = 0; i < length; ++i) { if (isKanaLetter(characters[i])) @@ -1987,7 +2041,8 @@ static inline bool isSeparator(UChar32 character) } inline SearchBuffer::SearchBuffer(const String& target, FindOptions options) - : m_target(target) + : m_target(foldQuoteMarks(target)) + , m_targetCharacters(StringView(m_target).upconvertedCharacters()) , m_options(options) , m_prefixLength(0) , m_atBreak(true) @@ -1996,18 +2051,13 @@ inline SearchBuffer::SearchBuffer(const String& target, FindOptions options) { ASSERT(!m_target.isEmpty()); - // FIXME: We'd like to tailor the searcher to fold quote marks for us instead - // of doing it in a separate replacement pass here, but ICU doesn't offer a way - // to add tailoring on top of the locale-specific tailoring as of this writing. - foldQuoteMarksAndSoftHyphens(m_target); - size_t targetLength = m_target.length(); m_buffer.reserveInitialCapacity(std::max(targetLength * 8, minimumSearchBufferSize)); m_overlap = m_buffer.capacity() / 4; if ((m_options & AtWordStarts) && targetLength) { UChar32 targetFirstCharacter; - U16_GET(m_target.deprecatedCharacters(), 0, 0, targetLength, targetFirstCharacter); + U16_GET(m_target, 0, 0u, targetLength, targetFirstCharacter); // Characters in the separator category never really occur at the beginning of a word, // so if the target begins with such a character, we just ignore the AtWordStart option. if (isSeparator(targetFirstCharacter)) { @@ -2044,12 +2094,12 @@ inline SearchBuffer::SearchBuffer(const String& target, FindOptions options) usearch_setAttribute(searcher, USEARCH_ELEMENT_COMPARISON, comparator, &status); ASSERT(status == U_ZERO_ERROR); - usearch_setPattern(searcher, m_target.deprecatedCharacters(), targetLength, &status); + usearch_setPattern(searcher, m_targetCharacters, targetLength, &status); ASSERT(status == U_ZERO_ERROR); // The kana workaround requires a normalized copy of the target string. if (m_targetRequiresKanaWorkaround) - normalizeCharacters(m_target.deprecatedCharacters(), m_target.length(), m_normalizedTarget); + normalizeCharacters(m_targetCharacters, targetLength, m_normalizedTarget); } inline SearchBuffer::~SearchBuffer() @@ -2064,9 +2114,9 @@ inline SearchBuffer::~SearchBuffer() unlockSearcher(); } -inline size_t SearchBuffer::append(const UChar* characters, size_t length) +inline size_t SearchBuffer::append(StringView text) { - ASSERT(length); + ASSERT(text.length()); if (m_atBreak) { m_buffer.shrink(0); @@ -2079,10 +2129,11 @@ inline size_t SearchBuffer::append(const UChar* characters, size_t length) } size_t oldLength = m_buffer.size(); - size_t usableLength = std::min(m_buffer.capacity() - oldLength, length); + size_t usableLength = std::min<size_t>(m_buffer.capacity() - oldLength, text.length()); ASSERT(usableLength); - m_buffer.append(characters, usableLength); - foldQuoteMarksAndSoftHyphens(m_buffer.data() + oldLength, usableLength); + m_buffer.grow(oldLength + usableLength); + for (unsigned i = 0; i < usableLength; ++i) + m_buffer[oldLength + i] = foldQuoteMark(text[i]); return usableLength; } @@ -2091,24 +2142,24 @@ inline bool SearchBuffer::needsMoreContext() const return m_needsMoreContext; } -inline void SearchBuffer::prependContext(const UChar* characters, size_t length) +inline void SearchBuffer::prependContext(StringView text) { ASSERT(m_needsMoreContext); ASSERT(m_prefixLength == m_buffer.size()); - if (!length) + if (!text.length()) return; m_atBreak = false; - size_t wordBoundaryContextStart = length; + size_t wordBoundaryContextStart = text.length(); if (wordBoundaryContextStart) { - U16_BACK_1(characters, 0, wordBoundaryContextStart); - wordBoundaryContextStart = startOfLastWordBoundaryContext(characters, wordBoundaryContextStart); + U16_BACK_1(text, 0, wordBoundaryContextStart); + wordBoundaryContextStart = startOfLastWordBoundaryContext(text.substring(0, wordBoundaryContextStart)); } - size_t usableLength = std::min(m_buffer.capacity() - m_prefixLength, length - wordBoundaryContextStart); - m_buffer.insert(0, characters + length - usableLength, usableLength); + size_t usableLength = std::min(m_buffer.capacity() - m_prefixLength, text.length() - wordBoundaryContextStart); + WTF::append(m_buffer, text.substring(text.length() - usableLength, usableLength)); m_prefixLength += usableLength; if (wordBoundaryContextStart || m_prefixLength == m_buffer.capacity()) @@ -2184,6 +2235,17 @@ inline bool SearchBuffer::isBadMatch(const UChar* match, size_t matchLength) con } } } + +inline bool SearchBuffer::isWordEndMatch(size_t start, size_t length) const +{ + ASSERT(length); + ASSERT(m_options & AtWordEnds); + + int endWord; + // Start searching at the end of matched search, so that multiple word matches succeed. + findEndWordBoundary(StringView(m_buffer.data(), m_buffer.size()), start + length - 1, &endWord); + return static_cast<size_t>(endWord) == (start + length); +} inline bool SearchBuffer::isWordStartMatch(size_t start, size_t length) const { @@ -2231,12 +2293,12 @@ inline bool SearchBuffer::isWordStartMatch(size_t start, size_t length) const // Chinese and Japanese lack word boundary marks, and there is no clear agreement on what constitutes // a word, so treat the position before any CJK character as a word start. - if (Font::isCJKIdeographOrSymbol(firstCharacter)) + if (FontCascade::isCJKIdeographOrSymbol(firstCharacter)) return true; size_t wordBreakSearchStart = start + length; while (wordBreakSearchStart > start) - wordBreakSearchStart = findNextWordFromIndex(m_buffer.data(), m_buffer.size(), wordBreakSearchStart, false /* backwards */); + wordBreakSearchStart = findNextWordFromIndex(StringView(m_buffer.data(), m_buffer.size()), wordBreakSearchStart, false /* backwards */); return wordBreakSearchStart == start; } @@ -2277,9 +2339,9 @@ nextMatch: if (m_options & AtWordStarts) { // Ensure that there is sufficient context before matchStart the next time around for // determining if it is at a word boundary. - int wordBoundaryContextStart = matchStart; + unsigned wordBoundaryContextStart = matchStart; U16_BACK_1(m_buffer.data(), 0, wordBoundaryContextStart); - wordBoundaryContextStart = startOfLastWordBoundaryContext(m_buffer.data(), wordBoundaryContextStart); + wordBoundaryContextStart = startOfLastWordBoundaryContext(StringView(m_buffer.data(), wordBoundaryContextStart)); overlap = std::min(size - 1, std::max(overlap, size - wordBoundaryContextStart)); } memcpy(m_buffer.data(), m_buffer.data() + size - overlap, overlap * sizeof(UChar)); @@ -2292,7 +2354,9 @@ nextMatch: ASSERT_WITH_SECURITY_IMPLICATION(matchStart + matchedLength <= size); // If this match is "bad", move on to the next match. - if (isBadMatch(m_buffer.data() + matchStart, matchedLength) || ((m_options & AtWordStarts) && !isWordStartMatch(matchStart, matchedLength))) { + if (isBadMatch(m_buffer.data() + matchStart, matchedLength) + || ((m_options & AtWordStarts) && !isWordStartMatch(matchStart, matchedLength)) + || ((m_options & AtWordEnds) && !isWordEndMatch(matchStart, matchedLength))) { matchStart = usearch_next(searcher, &status); ASSERT(status == U_ZERO_ERROR); goto nextMatch; @@ -2319,7 +2383,7 @@ inline SearchBuffer::SearchBuffer(const String& target, FindOptions options) { ASSERT(!m_target.isEmpty()); m_target.replace(noBreakSpace, ' '); - foldQuoteMarksAndSoftHyphens(m_target); + foldQuoteMarks(m_target); } inline SearchBuffer::~SearchBuffer() @@ -2339,7 +2403,7 @@ inline bool SearchBuffer::atBreak() const inline void SearchBuffer::append(UChar c, bool isStart) { - m_buffer[m_cursor] = c == noBreakSpace ? ' ' : foldQuoteMarkOrSoftHyphen(c); + m_buffer[m_cursor] = c == noBreakSpace ? ' ' : foldQuoteMark(c); m_isCharacterStartBuffer[m_cursor] = isStart; if (++m_cursor == m_target.length()) { m_cursor = 0; @@ -2421,109 +2485,108 @@ size_t SearchBuffer::length() const // -------- -int TextIterator::rangeLength(const Range* r, bool forSelectionPreservation) +int TextIterator::rangeLength(const Range* range, bool forSelectionPreservation) { - int length = 0; - for (TextIterator it(r, forSelectionPreservation ? TextIteratorEmitsCharactersBetweenAllVisiblePositions : TextIteratorDefaultBehavior); !it.atEnd(); it.advance()) - length += it.length(); - + unsigned length = 0; + for (TextIterator it(range, forSelectionPreservation ? TextIteratorEmitsCharactersBetweenAllVisiblePositions : TextIteratorDefaultBehavior); !it.atEnd(); it.advance()) + length += it.text().length(); return length; } -PassRefPtr<Range> TextIterator::subrange(Range* entireRange, int characterOffset, int characterCount) +Ref<Range> TextIterator::subrange(Range* entireRange, int characterOffset, int characterCount) +{ + CharacterIterator entireRangeIterator(*entireRange); + return characterSubrange(entireRange->ownerDocument(), entireRangeIterator, characterOffset, characterCount); +} + +static inline bool isInsideReplacedElement(TextIterator& iterator) { - CharacterIterator entireRangeIterator(entireRange); - return characterSubrange(entireRangeIterator, characterOffset, characterCount); + ASSERT(!iterator.atEnd()); + ASSERT(iterator.text().length() == 1); + Node* node = iterator.node(); + return node && isRendererReplacedElement(node->renderer()); } -PassRefPtr<Range> TextIterator::rangeFromLocationAndLength(ContainerNode* scope, int rangeLocation, int rangeLength, bool forSelectionPreservation) +RefPtr<Range> TextIterator::rangeFromLocationAndLength(ContainerNode* scope, int rangeLocation, int rangeLength, bool forSelectionPreservation) { - RefPtr<Range> resultRange = scope->document().createRange(); + Ref<Range> resultRange = scope->document().createRange(); int docTextPosition = 0; int rangeEnd = rangeLocation + rangeLength; bool startRangeFound = false; - RefPtr<Range> textRunRange; + Ref<Range> textRunRange = rangeOfContents(*scope); - TextIterator it(rangeOfContents(*scope).get(), forSelectionPreservation ? TextIteratorEmitsCharactersBetweenAllVisiblePositions : TextIteratorDefaultBehavior); + TextIterator it(textRunRange.ptr(), forSelectionPreservation ? TextIteratorEmitsCharactersBetweenAllVisiblePositions : TextIteratorDefaultBehavior); // FIXME: the atEnd() check shouldn't be necessary, workaround for <http://bugs.webkit.org/show_bug.cgi?id=6289>. - if (rangeLocation == 0 && rangeLength == 0 && it.atEnd()) { - textRunRange = it.range(); - - resultRange->setStart(textRunRange->startContainer(), 0, ASSERT_NO_EXCEPTION); - resultRange->setEnd(textRunRange->startContainer(), 0, ASSERT_NO_EXCEPTION); - - return resultRange.release(); + if (!rangeLocation && !rangeLength && it.atEnd()) { + resultRange->setStart(textRunRange->startContainer(), 0); + resultRange->setEnd(textRunRange->startContainer(), 0); + return WTFMove(resultRange); } for (; !it.atEnd(); it.advance()) { - int len = it.length(); + int length = it.text().length(); textRunRange = it.range(); - - bool foundStart = rangeLocation >= docTextPosition && rangeLocation <= docTextPosition + len; - bool foundEnd = rangeEnd >= docTextPosition && rangeEnd <= docTextPosition + len; - - // Fix textRunRange->endPosition(), but only if foundStart || foundEnd, because it is only - // in those cases that textRunRange is used. + + bool foundStart = rangeLocation >= docTextPosition && rangeLocation <= docTextPosition + length; + bool foundEnd = rangeEnd >= docTextPosition && rangeEnd <= docTextPosition + length; + if (foundEnd) { // FIXME: This is a workaround for the fact that the end of a run is often at the wrong - // position for emitted '\n's. - if (len == 1 && it.characterAt(0) == '\n') { + // position for emitted '\n's or if the renderer of the current node is a replaced element. + if (length == 1 && (it.text()[0] == '\n' || isInsideReplacedElement(it))) { it.advance(); if (!it.atEnd()) { - RefPtr<Range> range = it.range(); - textRunRange->setEnd(range->startContainer(), range->startOffset(), ASSERT_NO_EXCEPTION); + Ref<Range> range = it.range(); + textRunRange->setEnd(range->startContainer(), range->startOffset()); } else { Position runStart = textRunRange->startPosition(); Position runEnd = VisiblePosition(runStart).next().deepEquivalent(); if (runEnd.isNotNull()) - textRunRange->setEnd(runEnd.containerNode(), runEnd.computeOffsetInContainerNode(), ASSERT_NO_EXCEPTION); + textRunRange->setEnd(*runEnd.containerNode(), runEnd.computeOffsetInContainerNode()); } } } if (foundStart) { startRangeFound = true; - int exception = 0; - if (textRunRange->startContainer()->isTextNode()) { + if (textRunRange->startContainer().isTextNode()) { int offset = rangeLocation - docTextPosition; - resultRange->setStart(textRunRange->startContainer(), offset + textRunRange->startOffset(), exception); + resultRange->setStart(textRunRange->startContainer(), offset + textRunRange->startOffset()); } else { if (rangeLocation == docTextPosition) - resultRange->setStart(textRunRange->startContainer(), textRunRange->startOffset(), exception); + resultRange->setStart(textRunRange->startContainer(), textRunRange->startOffset()); else - resultRange->setStart(textRunRange->endContainer(), textRunRange->endOffset(), exception); + resultRange->setStart(textRunRange->endContainer(), textRunRange->endOffset()); } } if (foundEnd) { - int exception = 0; - if (textRunRange->startContainer()->isTextNode()) { + if (textRunRange->startContainer().isTextNode()) { int offset = rangeEnd - docTextPosition; - resultRange->setEnd(textRunRange->startContainer(), offset + textRunRange->startOffset(), exception); + resultRange->setEnd(textRunRange->startContainer(), offset + textRunRange->startOffset()); } else { if (rangeEnd == docTextPosition) - resultRange->setEnd(textRunRange->startContainer(), textRunRange->startOffset(), exception); + resultRange->setEnd(textRunRange->startContainer(), textRunRange->startOffset()); else - resultRange->setEnd(textRunRange->endContainer(), textRunRange->endOffset(), exception); + resultRange->setEnd(textRunRange->endContainer(), textRunRange->endOffset()); } - docTextPosition += len; + docTextPosition += length; break; } - docTextPosition += len; + + docTextPosition += length; } if (!startRangeFound) - return 0; + return nullptr; - if (rangeLength != 0 && rangeEnd > docTextPosition) { // rangeEnd is out of bounds - int exception = 0; - resultRange->setEnd(textRunRange->endContainer(), textRunRange->endOffset(), exception); - } + if (rangeLength && rangeEnd > docTextPosition) // rangeEnd is out of bounds + resultRange->setEnd(textRunRange->endContainer(), textRunRange->endOffset()); - return resultRange.release(); + return WTFMove(resultRange); } bool TextIterator::getLocationAndLengthFromRange(Node* scope, const Range* range, size_t& location, size_t& length) @@ -2531,26 +2594,23 @@ bool TextIterator::getLocationAndLengthFromRange(Node* scope, const Range* range location = notFound; length = 0; - if (!range->startContainer()) - return false; - // The critical assumption is that this only gets called with ranges that // concentrate on a given area containing the selection root. This is done // because of text fields and textareas. The DOM for those is not // directly in the document DOM, so ensure that the range does not cross a // boundary of one of those. - if (range->startContainer() != scope && !range->startContainer()->isDescendantOf(scope)) + if (&range->startContainer() != scope && !range->startContainer().isDescendantOf(scope)) return false; - if (range->endContainer() != scope && !range->endContainer()->isDescendantOf(scope)) + if (&range->endContainer() != scope && !range->endContainer().isDescendantOf(scope)) return false; - RefPtr<Range> testRange = Range::create(scope->document(), scope, 0, range->startContainer(), range->startOffset()); - ASSERT(testRange->startContainer() == scope); - location = TextIterator::rangeLength(testRange.get()); + Ref<Range> testRange = Range::create(scope->document(), scope, 0, &range->startContainer(), range->startOffset()); + ASSERT(&testRange->startContainer() == scope); + location = TextIterator::rangeLength(testRange.ptr()); - testRange->setEnd(range->endContainer(), range->endOffset(), IGNORE_EXCEPTION); - ASSERT(testRange->startContainer() == scope); - length = TextIterator::rangeLength(testRange.get()) - location; + testRange->setEnd(range->endContainer(), range->endOffset()); + ASSERT(&testRange->startContainer() == scope); + length = TextIterator::rangeLength(testRange.ptr()) - location; return true; } @@ -2570,7 +2630,7 @@ String plainText(const Range* r, TextIteratorBehavior defaultBehavior, bool isDi for (TextIterator it(r, behavior); !it.atEnd(); it.advance()) { it.appendTextToStringBuilder(builder); - bufferLength += it.length(); + bufferLength += it.text().length(); } if (!bufferLength) @@ -2584,71 +2644,101 @@ String plainText(const Range* r, TextIteratorBehavior defaultBehavior, bool isDi return result; } -static PassRefPtr<Range> collapsedToBoundary(const Range* range, bool forward) +String plainTextReplacingNoBreakSpace(const Range* range, TextIteratorBehavior defaultBehavior, bool isDisplayString) { - RefPtr<Range> result = range->cloneRange(ASSERT_NO_EXCEPTION); - result->collapse(!forward, ASSERT_NO_EXCEPTION); - return result.release(); + return plainText(range, defaultBehavior, isDisplayString).replace(noBreakSpace, ' '); } -static size_t findPlainText(CharacterIterator& it, const String& target, FindOptions options, size_t& matchStart) +static Ref<Range> collapsedToBoundary(const Range& range, bool forward) { - matchStart = 0; - size_t matchLength = 0; + Ref<Range> result = range.cloneRange(); + result->collapse(!forward); + return result; +} - SearchBuffer buffer(target, options); +static TextIteratorBehavior findIteratorOptions(FindOptions options) +{ + TextIteratorBehavior iteratorOptions = TextIteratorEntersTextControls | TextIteratorClipsToFrameAncestors; + if (!(options & DoNotTraverseFlatTree)) + iteratorOptions |= TextIteratorTraversesFlatTree; + return iteratorOptions; +} +static void findPlainTextMatches(const Range& range, const String& target, FindOptions options, const std::function<bool(size_t, size_t)>& match) +{ + SearchBuffer buffer(target, options); if (buffer.needsMoreContext()) { - RefPtr<Range> startRange = it.range(); - RefPtr<Range> beforeStartRange = startRange->ownerDocument().createRange(); - beforeStartRange->setEnd(startRange->startContainer(), startRange->startOffset(), IGNORE_EXCEPTION); + Ref<Range> beforeStartRange = range.ownerDocument().createRange(); + beforeStartRange->setEnd(range.startContainer(), range.startOffset()); for (SimplifiedBackwardsTextIterator backwardsIterator(beforeStartRange.get()); !backwardsIterator.atEnd(); backwardsIterator.advance()) { - buffer.prependContext(backwardsIterator.characters(), backwardsIterator.length()); + buffer.prependContext(backwardsIterator.text()); if (!buffer.needsMoreContext()) break; } } - while (!it.atEnd()) { - it.advance(buffer.append(it.characters(), it.length())); -tryAgain: - size_t matchStartOffset; - if (size_t newMatchLength = buffer.search(matchStartOffset)) { - // Note that we found a match, and where we found it. - size_t lastCharacterInBufferOffset = it.characterOffset(); - ASSERT(lastCharacterInBufferOffset >= matchStartOffset); - matchStart = lastCharacterInBufferOffset - matchStartOffset; - matchLength = newMatchLength; - // If searching forward, stop on the first match. - // If searching backward, don't stop, so we end up with the last match. - if (!(options & Backwards)) + CharacterIterator findIterator(range, findIteratorOptions(options)); + while (!findIterator.atEnd()) { + findIterator.advance(buffer.append(findIterator.text())); + while (1) { + size_t matchStartOffset; + size_t newMatchLength = buffer.search(matchStartOffset); + if (!newMatchLength) { + if (findIterator.atBreak() && !buffer.atBreak()) { + buffer.reachedBreak(); + continue; + } break; - goto tryAgain; - } - if (it.atBreak() && !buffer.atBreak()) { - buffer.reachedBreak(); - goto tryAgain; + } + size_t lastCharacterInBufferOffset = findIterator.characterOffset(); + ASSERT(lastCharacterInBufferOffset >= matchStartOffset); + if (match(lastCharacterInBufferOffset - matchStartOffset, newMatchLength)) + return; } } +} - return matchLength; +static Ref<Range> rangeForMatch(const Range& range, FindOptions options, size_t matchStart, size_t matchLength, bool searchForward) +{ + if (!matchLength) + return collapsedToBoundary(range, searchForward); + CharacterIterator rangeComputeIterator(range, findIteratorOptions(options)); + return characterSubrange(range.ownerDocument(), rangeComputeIterator, matchStart, matchLength); } -PassRefPtr<Range> findPlainText(const Range* range, const String& target, FindOptions options) +Ref<Range> findClosestPlainText(const Range& range, const String& target, FindOptions options, unsigned targetOffset) { - // First, find the text. - size_t matchStart; - size_t matchLength; - { - CharacterIterator findIterator(range, TextIteratorEntersTextControls); - matchLength = findPlainText(findIterator, target, options, matchStart); - if (!matchLength) - return collapsedToBoundary(range, !(options & Backwards)); - } + size_t matchStart = 0; + size_t matchLength = 0; + size_t distance = std::numeric_limits<size_t>::max(); + auto match = [targetOffset, &distance, &matchStart, &matchLength] (size_t start, size_t length) { + size_t newDistance = std::min(abs(static_cast<signed>(start - targetOffset)), abs(static_cast<signed>(start + length - targetOffset))); + if (newDistance < distance) { + matchStart = start; + matchLength = length; + distance = newDistance; + } + return false; + }; + + findPlainTextMatches(range, target, options, match); + return rangeForMatch(range, options, matchStart, matchLength, !(options & Backwards)); +} + +Ref<Range> findPlainText(const Range& range, const String& target, FindOptions options) +{ + bool searchForward = !(options & Backwards); + size_t matchStart = 0; + size_t matchLength = 0; + auto match = [searchForward, &matchStart, &matchLength] (size_t start, size_t length) { + matchStart = start; + matchLength = length; + // Look for the last match when searching backwards instead. + return searchForward; + }; - // Then, find the document position of the start and the end of the text. - CharacterIterator computeRangeIterator(range, TextIteratorEntersTextControls); - return characterSubrange(computeRangeIterator, matchStart, matchLength); + findPlainTextMatches(range, target, options, match); + return rangeForMatch(range, options, matchStart, matchLength, searchForward); } } diff --git a/Source/WebCore/editing/TextIterator.h b/Source/WebCore/editing/TextIterator.h index 8b8919f41..c0d0851f4 100644 --- a/Source/WebCore/editing/TextIterator.h +++ b/Source/WebCore/editing/TextIterator.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2006, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2004, 2006, 2009, 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,46 +23,31 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TextIterator_h -#define TextIterator_h +#pragma once + +// FIXME: Move each iterator class into a separate header file. #include "FindOptions.h" #include "Range.h" +#include "TextIteratorBehavior.h" #include <wtf/Vector.h> +#include <wtf/text/StringView.h> namespace WebCore { class InlineTextBox; class RenderText; class RenderTextFragment; - -enum TextIteratorBehavior { - TextIteratorDefaultBehavior = 0, - TextIteratorEmitsCharactersBetweenAllVisiblePositions = 1 << 0, - TextIteratorEntersTextControls = 1 << 1, - TextIteratorEmitsTextsWithoutTranscoding = 1 << 2, - TextIteratorIgnoresStyleVisibility = 1 << 3, - TextIteratorEmitsObjectReplacementCharacters = 1 << 4, - TextIteratorEmitsOriginalText = 1 << 5, - TextIteratorStopsOnFormControls = 1 << 6, - TextIteratorEmitsImageAltText = 1 << 7, -}; - -// FIXME: Can't really answer this question correctly without knowing the white-space mode. -// FIXME: Move this somewhere else in the editing directory. It doesn't belong here. -inline bool isCollapsibleWhitespace(UChar c) -{ - switch (c) { - case ' ': - case '\n': - return true; - default: - return false; - } +namespace SimpleLineLayout { +class RunResolver; } -String plainText(const Range*, TextIteratorBehavior defaultBehavior = TextIteratorDefaultBehavior, bool isDisplayString = false); -PassRefPtr<Range> findPlainText(const Range*, const String&, FindOptions); +WEBCORE_EXPORT String plainText(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior, bool isDisplayString = false); +WEBCORE_EXPORT String plainTextReplacingNoBreakSpace(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior, bool isDisplayString = false); +Ref<Range> findPlainText(const Range&, const String&, FindOptions); +WEBCORE_EXPORT Ref<Range> findClosestPlainText(const Range&, const String&, FindOptions, unsigned); + +// FIXME: Move this somewhere else in the editing directory. It doesn't belong here. bool isRendererReplacedElement(RenderObject*); class BitStack { @@ -81,121 +66,123 @@ private: Vector<unsigned, 1> m_words; }; +class TextIteratorCopyableText { +public: + TextIteratorCopyableText() + : m_singleCharacter(0) + , m_offset(0) + , m_length(0) + { + } + + StringView text() const { return m_singleCharacter ? StringView(&m_singleCharacter, 1) : StringView(m_string).substring(m_offset, m_length); } + void appendToStringBuilder(StringBuilder&) const; + + void reset(); + void set(String&&); + void set(String&&, unsigned offset, unsigned length); + void set(UChar); + +private: + UChar m_singleCharacter; + String m_string; + unsigned m_offset; + unsigned m_length; +}; + // Iterates through the DOM range, returning all the text, and 0-length boundaries -// at points where replaced elements break up the text flow. The text comes back in -// chunks so as to optimize for performance of the iteration. +// at points where replaced elements break up the text flow. The text is delivered in +// the chunks it's already stored in, to avoid copying any text. class TextIterator { public: - explicit TextIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); - ~TextIterator(); + WEBCORE_EXPORT explicit TextIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); + WEBCORE_EXPORT ~TextIterator(); + + bool atEnd() const { return !m_positionNode; } + WEBCORE_EXPORT void advance(); + + StringView text() const { ASSERT(!atEnd()); return m_text; } + WEBCORE_EXPORT Ref<Range> range() const; + WEBCORE_EXPORT Node* node() const; + + const TextIteratorCopyableText& copyableText() const { ASSERT(!atEnd()); return m_copyableText; } + void appendTextToStringBuilder(StringBuilder& builder) const { copyableText().appendToStringBuilder(builder); } + + WEBCORE_EXPORT static int rangeLength(const Range*, bool spacesForReplacedElements = false); + WEBCORE_EXPORT static RefPtr<Range> rangeFromLocationAndLength(ContainerNode* scope, int rangeLocation, int rangeLength, bool spacesForReplacedElements = false); + WEBCORE_EXPORT static bool getLocationAndLengthFromRange(Node* scope, const Range*, size_t& location, size_t& length); + WEBCORE_EXPORT static Ref<Range> subrange(Range* entireRange, int characterOffset, int characterCount); - bool atEnd() const { return !m_positionNode || m_shouldStop; } - void advance(); - - int length() const { return m_textLength; } - const UChar* characters() const { return m_textCharacters ? m_textCharacters : m_text.deprecatedCharacters() + startOffset(); } - UChar characterAt(unsigned index) const; - void appendTextToStringBuilder(StringBuilder&) const; - - PassRefPtr<Range> range() const; - Node* node() const; - - static int rangeLength(const Range*, bool spacesForReplacedElements = false); - static PassRefPtr<Range> rangeFromLocationAndLength(ContainerNode* scope, int rangeLocation, int rangeLength, bool spacesForReplacedElements = false); - static bool getLocationAndLengthFromRange(Node* scope, const Range*, size_t& location, size_t& length); - static PassRefPtr<Range> subrange(Range* entireRange, int characterOffset, int characterCount); - private: - int startOffset() const { return m_positionStartOffset; } - const String& string() const { return m_text; } - void exitNode(); + void exitNode(Node*); bool shouldRepresentNodeOffsetZero(); - bool shouldEmitSpaceBeforeAndAfterNode(Node*); + bool shouldEmitSpaceBeforeAndAfterNode(Node&); void representNodeOffsetZero(); bool handleTextNode(); bool handleReplacedElement(); bool handleNonTextNode(); void handleTextBox(); - void handleTextNodeFirstLetter(RenderTextFragment*); - bool hasVisibleTextNode(RenderText*); - void emitCharacter(UChar, Node* textNode, Node* offsetBaseNode, int textStartOffset, int textEndOffset); - void emitText(Node* textNode, RenderObject* renderObject, int textStartOffset, int textEndOffset); - void emitText(Node* textNode, int textStartOffset, int textEndOffset); - - // Current position, not necessarily of the text being returned, but position - // as we walk through the DOM tree. - Node* m_node; - int m_offset; - bool m_handledNode; - bool m_handledChildren; + void handleTextNodeFirstLetter(RenderTextFragment&); + void emitCharacter(UChar, Node& characterNode, Node* offsetBaseNode, int textStartOffset, int textEndOffset); + void emitText(Text& textNode, RenderText&, int textStartOffset, int textEndOffset); + + Node* baseNodeForEmittingNewLine() const; + + const TextIteratorBehavior m_behavior { TextIteratorDefaultBehavior }; + + // Current position, not necessarily of the text being returned, but position as we walk through the DOM tree. + Node* m_node { nullptr }; + int m_offset { 0 }; + bool m_handledNode { false }; + bool m_handledChildren { false }; BitStack m_fullyClippedStack; - + // The range. - Node* m_startContainer; - int m_startOffset; - Node* m_endContainer; - int m_endOffset; - Node* m_pastEndNode; - + Node* m_startContainer { nullptr }; + int m_startOffset { 0 }; + Node* m_endContainer { nullptr }; + int m_endOffset { 0 }; + Node* m_pastEndNode { nullptr }; + // The current text and its position, in the form to be returned from the iterator. - Node* m_positionNode; - mutable Node* m_positionOffsetBaseNode; - mutable int m_positionStartOffset; - mutable int m_positionEndOffset; - const UChar* m_textCharacters; // If null, then use m_text for character data. - int m_textLength; - // Hold string m_textCharacters points to so we ensure it won't be deleted. - String m_text; - - // Used when there is still some pending text from the current node; when these - // are false and 0, we go back to normal iterating. - bool m_needsAnotherNewline; - InlineTextBox* m_textBox; - // Used when iteration over :first-letter text to save pointer to - // remaining text box. - InlineTextBox* m_remainingTextBox; + Node* m_positionNode { nullptr }; + mutable Node* m_positionOffsetBaseNode { nullptr }; + mutable int m_positionStartOffset { 0 }; + mutable int m_positionEndOffset { 0 }; + TextIteratorCopyableText m_copyableText; + StringView m_text; + + // Used when there is still some pending text from the current node; when these are false and null, we go back to normal iterating. + Node* m_nodeForAdditionalNewline { nullptr }; + InlineTextBox* m_textBox { nullptr }; + + // Used when iterating over :first-letter text to save pointer to remaining text box. + InlineTextBox* m_remainingTextBox { nullptr }; + // Used to point to RenderText object for :first-letter. - RenderText *m_firstLetterText; - + RenderText* m_firstLetterText { nullptr }; + // Used to do the whitespace collapsing logic. - Node* m_lastTextNode; - bool m_lastTextNodeEndedWithCollapsedSpace; - UChar m_lastCharacter; - - // Used for whitespace characters that aren't in the DOM, so we can point at them. - UChar m_singleCharacterBuffer; - - // Used when text boxes are out of order (Hebrew/Arabic w/ embeded LTR text) + Text* m_lastTextNode { nullptr }; + bool m_lastTextNodeEndedWithCollapsedSpace { false }; + UChar m_lastCharacter { 0 }; + + // Used to do simple line layout run logic. + bool m_nextRunNeedsWhitespace { false }; + unsigned m_accumulatedSimpleTextLengthInFlow { 0 }; + Text* m_previousSimpleTextNodeInFlow { nullptr }; + std::unique_ptr<SimpleLineLayout::RunResolver> m_flowRunResolverCache; + + // Used when text boxes are out of order (Hebrew/Arabic with embedded LTR text) Vector<InlineTextBox*> m_sortedTextBoxes; - size_t m_sortedTextBoxesPosition; - + size_t m_sortedTextBoxesPosition { 0 }; + // Used when deciding whether to emit a "positioning" (e.g. newline) before any other content - bool m_hasEmitted; - - // Used by selection preservation code. There should be one character emitted between every VisiblePosition - // in the Range used to create the TextIterator. - // FIXME <rdar://problem/6028818>: This functionality should eventually be phased out when we rewrite - // moveParagraphs to not clone/destroy moved content. - bool m_emitsCharactersBetweenAllVisiblePositions; - bool m_entersTextControls; - - // Used when we want texts for copying, pasting, and transposing. - bool m_emitsTextWithoutTranscoding; - // Used in pasting inside password field. - bool m_emitsOriginalText; + bool m_hasEmitted { false }; + // Used when deciding text fragment created by :first-letter should be looked into. - bool m_handledFirstLetter; - // Used when the visibility of the style should not affect text gathering. - bool m_ignoresStyleVisibility; - // Used when emitting the special 0xFFFC character is required. Children for replaced objects will be ignored. - 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; - - bool m_emitsImageAltText; + bool m_handledFirstLetter { false }; }; // Iterates through the DOM range, returning all the text, and 0-length boundaries @@ -203,147 +190,119 @@ private: // chunks so as to optimize for performance of the iteration. class SimplifiedBackwardsTextIterator { public: - explicit SimplifiedBackwardsTextIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); - - bool atEnd() const { return !m_positionNode || m_shouldStop; } + explicit SimplifiedBackwardsTextIterator(const Range&); + + bool atEnd() const { return !m_positionNode; } void advance(); - Node* node() const { return m_node; } - int length() const { return m_textLength; } - const UChar* characters() const { return m_textCharacters; } - - PassRefPtr<Range> range() const; - + StringView text() const { ASSERT(!atEnd()); return m_text; } + WEBCORE_EXPORT Ref<Range> range() const; + Node* node() const { ASSERT(!atEnd()); return m_node; } + private: void exitNode(); bool handleTextNode(); RenderText* handleFirstLetter(int& startOffset, int& offsetInNode); bool handleReplacedElement(); bool handleNonTextNode(); - void emitCharacter(UChar, Node*, int startOffset, int endOffset); + void emitCharacter(UChar, Node&, int startOffset, int endOffset); bool advanceRespectingRange(Node*); - // Current position, not necessarily of the text being returned, but position - // as we walk through the DOM tree. - Node* m_node; - int m_offset; - bool m_handledNode; - bool m_handledChildren; + const TextIteratorBehavior m_behavior { TextIteratorDefaultBehavior }; + + // Current position, not necessarily of the text being returned, but position as we walk through the DOM tree. + Node* m_node { nullptr }; + int m_offset { 0 }; + bool m_handledNode { false }; + bool m_handledChildren { false }; BitStack m_fullyClippedStack; - // End of the range. - Node* m_startNode; - int m_startOffset; - // Start of the range. - Node* m_endNode; - int m_endOffset; + // The range. + Node* m_startContainer { nullptr }; + int m_startOffset { 0 }; + Node* m_endContainer { nullptr }; + int m_endOffset { 0 }; // The current text and its position, in the form to be returned from the iterator. - Node* m_positionNode; - int m_positionStartOffset; - int m_positionEndOffset; - const UChar* m_textCharacters; - int m_textLength; + Node* m_positionNode { nullptr }; + int m_positionStartOffset { 0 }; + int m_positionEndOffset { 0 }; + TextIteratorCopyableText m_copyableText; + StringView m_text; // Used to do the whitespace logic. - Node* m_lastTextNode; - UChar m_lastCharacter; - - // Used for whitespace characters that aren't in the DOM, so we can point at them. - UChar m_singleCharacterBuffer; + Text* m_lastTextNode { nullptr }; + UChar m_lastCharacter { 0 }; - // Whether m_node has advanced beyond the iteration range (i.e. m_startNode). - bool m_havePassedStartNode; + // Whether m_node has advanced beyond the iteration range (i.e. m_startContainer). + bool m_havePassedStartContainer { false }; // 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; - - // Used in pasting inside password field. - bool m_emitsOriginalText; + bool m_shouldHandleFirstLetter { false }; }; // Builds on the text iterator, adding a character position so we can walk one // character at a time, or faster, as needed. Useful for searching. class CharacterIterator { public: - explicit CharacterIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); + explicit CharacterIterator(const Range&, TextIteratorBehavior = TextIteratorDefaultBehavior); + bool atEnd() const { return m_underlyingIterator.atEnd(); } void advance(int numCharacters); + StringView text() const { return m_underlyingIterator.text().substring(m_runOffset); } + Ref<Range> range() const; + bool atBreak() const { return m_atBreak; } - bool atEnd() const { return m_textIterator.atEnd(); } - - int length() const { return m_textIterator.length() - m_runOffset; } - const UChar* characters() const { return m_textIterator.characters() + m_runOffset; } - String string(int numChars); - int characterOffset() const { return m_offset; } - PassRefPtr<Range> range() const; - + private: + TextIterator m_underlyingIterator; + int m_offset; int m_runOffset; bool m_atBreak; - - TextIterator m_textIterator; }; class BackwardsCharacterIterator { public: - explicit BackwardsCharacterIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); - - void advance(int); + explicit BackwardsCharacterIterator(const Range&); - bool atEnd() const { return m_textIterator.atEnd(); } + bool atEnd() const { return m_underlyingIterator.atEnd(); } + void advance(int numCharacters); - PassRefPtr<Range> range() const; + Ref<Range> range() const; private: + SimplifiedBackwardsTextIterator m_underlyingIterator; + int m_offset; int m_runOffset; bool m_atBreak; - - SimplifiedBackwardsTextIterator m_textIterator; }; -// Very similar to the TextIterator, except that the chunks of text returned are "well behaved", -// meaning they never end split up a word. This is useful for spellcheck or (perhaps one day) searching. +// Similar to the TextIterator, except that the chunks of text returned are "well behaved", meaning +// they never split up a word. This is useful for spell checking and perhaps one day for searching as well. class WordAwareIterator { public: - explicit WordAwareIterator(const Range*); - ~WordAwareIterator(); + explicit WordAwareIterator(const Range&); - bool atEnd() const { return !m_didLookAhead && m_textIterator.atEnd(); } + bool atEnd() const { return !m_didLookAhead && m_underlyingIterator.atEnd(); } void advance(); - - int length() const; - const UChar* characters() const; - - // Range of the text we're currently returning - PassRefPtr<Range> range() const { return m_range; } + + StringView text() const; private: - // text from the previous chunk from the textIterator - const UChar* m_previousText; - int m_previousLength; + TextIterator m_underlyingIterator; + + // Text from the previous chunk from the text iterator. + TextIteratorCopyableText m_previousText; - // many chunks from textIterator concatenated + // Many chunks from text iterator concatenated. Vector<UChar> m_buffer; - // Did we have to look ahead in the textIterator to confirm the current chunk? + // Did we have to look ahead in the text iterator to confirm the current chunk? bool m_didLookAhead; - - RefPtr<Range> m_range; - - TextIterator m_textIterator; }; -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/editing/TextIteratorBehavior.h b/Source/WebCore/editing/TextIteratorBehavior.h new file mode 100644 index 000000000..c01f97f82 --- /dev/null +++ b/Source/WebCore/editing/TextIteratorBehavior.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2004, 2006, 2009, 2014 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. + */ + +#pragma once + +namespace WebCore { + +enum TextIteratorBehaviorFlag { + TextIteratorDefaultBehavior = 0, + + // Used by selection preservation code. There should be one character emitted between every VisiblePosition + // in the Range used to create the TextIterator. + // FIXME <rdar://problem/6028818>: This functionality should eventually be phased out when we rewrite + // moveParagraphs to not clone/destroy moved content. + TextIteratorEmitsCharactersBetweenAllVisiblePositions = 1 << 0, + + TextIteratorEntersTextControls = 1 << 1, + + // Used when we want text for copying, pasting, and transposing. + TextIteratorEmitsTextsWithoutTranscoding = 1 << 2, + + // Used when the visibility of the style should not affect text gathering. + TextIteratorIgnoresStyleVisibility = 1 << 3, + + // Used when emitting the special 0xFFFC character is required. Children for replaced objects will be ignored. + TextIteratorEmitsObjectReplacementCharacters = 1 << 4, + + // Used when pasting inside password field. + TextIteratorEmitsOriginalText = 1 << 5, + + TextIteratorEmitsImageAltText = 1 << 6, + + TextIteratorBehavesAsIfNodesFollowing = 1 << 7, + + // Makes visiblity test take into account the visibility of the frame. + // FIXME: This should probably be always on unless TextIteratorIgnoresStyleVisibility is set. + TextIteratorClipsToFrameAncestors = 1 << 8, + + TextIteratorTraversesFlatTree = 1 << 9, +}; + +typedef unsigned short TextIteratorBehavior; + +} // namespace WebCore diff --git a/Source/WebCore/editing/TypingCommand.cpp b/Source/WebCore/editing/TypingCommand.cpp index 1f8768787..b61304a7f 100644 --- a/Source/WebCore/editing/TypingCommand.cpp +++ b/Source/WebCore/editing/TypingCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2005-2008, 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -26,17 +26,25 @@ #include "config.h" #include "TypingCommand.h" +#include "AXObjectCache.h" #include "BreakBlockquoteCommand.h" +#include "DataTransfer.h" #include "DeleteSelectionCommand.h" #include "Document.h" #include "Editor.h" #include "Element.h" #include "Frame.h" +#include "HTMLElement.h" #include "HTMLNames.h" #include "InsertLineBreakCommand.h" #include "InsertParagraphSeparatorCommand.h" #include "InsertTextCommand.h" +#include "Logging.h" +#include "MarkupAccumulator.h" +#include "MathMLElement.h" #include "RenderElement.h" +#include "StaticRange.h" +#include "TextIterator.h" #include "VisibleUnits.h" #include "htmlediting.h" @@ -71,24 +79,89 @@ private: const String& m_text; }; +static inline EditAction editActionForTypingCommand(TypingCommand::ETypingCommand command, TextGranularity granularity, TypingCommand::TextCompositionType compositionType, bool isAutocompletion) +{ + if (compositionType == TypingCommand::TextCompositionPending) { + if (command == TypingCommand::InsertText) + return EditActionTypingInsertPendingComposition; + if (command == TypingCommand::DeleteSelection) + return EditActionTypingDeletePendingComposition; + ASSERT_NOT_REACHED(); + } + + if (compositionType == TypingCommand::TextCompositionFinal) { + if (command == TypingCommand::InsertText) + return EditActionTypingInsertFinalComposition; + if (command == TypingCommand::DeleteSelection) + return EditActionTypingDeleteFinalComposition; + ASSERT_NOT_REACHED(); + } + + switch (command) { + case TypingCommand::DeleteSelection: + return EditActionTypingDeleteSelection; + case TypingCommand::DeleteKey: { + if (granularity == WordGranularity) + return EditActionTypingDeleteWordBackward; + if (granularity == LineBoundary) + return EditActionTypingDeleteLineBackward; + return EditActionTypingDeleteBackward; + } + case TypingCommand::ForwardDeleteKey: + if (granularity == WordGranularity) + return EditActionTypingDeleteWordForward; + if (granularity == LineBoundary) + return EditActionTypingDeleteLineForward; + return EditActionTypingDeleteForward; + case TypingCommand::InsertText: + return isAutocompletion ? EditActionInsertReplacement : EditActionTypingInsertText; + case TypingCommand::InsertLineBreak: + return EditActionTypingInsertLineBreak; + case TypingCommand::InsertParagraphSeparator: + case TypingCommand::InsertParagraphSeparatorInQuotedContent: + return EditActionTypingInsertParagraph; + default: + return EditActionUnspecified; + } +} + +static inline bool editActionIsDeleteByTyping(EditAction action) +{ + switch (action) { + case EditActionTypingDeleteSelection: + case EditActionTypingDeleteBackward: + case EditActionTypingDeleteWordBackward: + case EditActionTypingDeleteLineBackward: + case EditActionTypingDeleteForward: + case EditActionTypingDeleteWordForward: + case EditActionTypingDeleteLineForward: + return true; + default: + return false; + } +} + TypingCommand::TypingCommand(Document& document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType) - : TextInsertionBaseCommand(document) + : TextInsertionBaseCommand(document, editActionForTypingCommand(commandType, granularity, compositionType, options & IsAutocompletion)) , m_commandType(commandType) , m_textToInsert(textToInsert) + , m_currentTextToInsert(textToInsert) , m_openForMoreTyping(true) , m_selectInsertedText(options & SelectInsertedText) , m_smartDelete(options & SmartDelete) , m_granularity(granularity) , m_compositionType(compositionType) - , m_killRing(options & KillRing) + , m_shouldAddToKillRing(options & AddsToKillRing) + , m_isAutocompletion(options & IsAutocompletion) , m_openedByBackwardDelete(false) , m_shouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator) , m_shouldPreventSpellChecking(options & PreventSpellChecking) { + m_currentTypingEditAction = editingAction(); updatePreservesTypingStyle(m_commandType); } -void TypingCommand::deleteSelection(Document& document, Options options) +void TypingCommand::deleteSelection(Document& document, Options options, TextCompositionType compositionType) { Frame* frame = document.frame(); ASSERT(frame); @@ -96,27 +169,31 @@ void TypingCommand::deleteSelection(Document& document, Options options) if (!frame->selection().isRange()) return; - if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) { + if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) { + lastTypingCommand->setIsAutocompletion(options & IsAutocompletion); + lastTypingCommand->setCompositionType(compositionType); lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); lastTypingCommand->deleteSelection(options & SmartDelete); return; } - TypingCommand::create(document, DeleteSelection, "", options)->apply(); + TypingCommand::create(document, DeleteSelection, emptyString(), options, compositionType)->apply(); } void TypingCommand::deleteKeyPressed(Document& document, Options options, TextGranularity granularity) { if (granularity == CharacterGranularity) { - if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) { + if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) { updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), document.frame()); + lastTypingCommand->setIsAutocompletion(options & IsAutocompletion); + lastTypingCommand->setCompositionType(TextCompositionNone); lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); - lastTypingCommand->deleteKeyPressed(granularity, options & KillRing); + lastTypingCommand->deleteKeyPressed(granularity, options & AddsToKillRing); return; } } - TypingCommand::create(document, DeleteKey, "", options, granularity)->apply(); + TypingCommand::create(document, DeleteKey, emptyString(), options, granularity)->apply(); } void TypingCommand::forwardDeleteKeyPressed(Document& document, Options options, TextGranularity granularity) @@ -124,15 +201,17 @@ void TypingCommand::forwardDeleteKeyPressed(Document& document, Options options, // FIXME: Forward delete in TextEdit appears to open and close a new typing command. Frame* frame = document.frame(); if (granularity == CharacterGranularity) { - if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) { + if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) { updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame); + lastTypingCommand->setIsAutocompletion(options & IsAutocompletion); + lastTypingCommand->setCompositionType(TextCompositionNone); lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); - lastTypingCommand->forwardDeleteKeyPressed(granularity, options & KillRing); + lastTypingCommand->forwardDeleteKeyPressed(granularity, options & AddsToKillRing); return; } } - TypingCommand::create(document, ForwardDeleteKey, "", options, granularity)->apply(); + TypingCommand::create(document, ForwardDeleteKey, emptyString(), options, granularity)->apply(); } void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand* typingCommand, Frame* frame) @@ -152,7 +231,7 @@ void TypingCommand::insertText(Document& document, const String& text, Options o ASSERT(frame); if (!text.isEmpty()) - frame->editor().updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text.deprecatedCharacters()[0])); + frame->editor().updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0])); insertText(document, text, frame->selection().selection(), options, composition); } @@ -163,45 +242,52 @@ void TypingCommand::insertText(Document& document, const String& text, const Vis RefPtr<Frame> frame = document.frame(); ASSERT(frame); + LOG(Editing, "TypingCommand::insertText (text %s)", text.utf8().data()); + VisibleSelection currentSelection = frame->selection().selection(); - String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionUpdate); + String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionPending); // 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 // to deal with custom selections in a general way that can be used by all of the commands. - if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame.get())) { + if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) { if (lastTypingCommand->endingSelection() != selectionForInsertion) { lastTypingCommand->setStartingSelection(selectionForInsertion); lastTypingCommand->setEndingSelection(selectionForInsertion); } + lastTypingCommand->setIsAutocompletion(options & IsAutocompletion); lastTypingCommand->setCompositionType(compositionType); lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); - lastTypingCommand->insertText(newText, options & SelectInsertedText); + lastTypingCommand->insertTextAndNotifyAccessibility(newText, options & SelectInsertedText); return; } RefPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, options, compositionType); - applyTextInsertionCommand(frame.get(), cmd, selectionForInsertion, currentSelection); + applyTextInsertionCommand(frame.get(), *cmd, selectionForInsertion, currentSelection); } void TypingCommand::insertLineBreak(Document& document, Options options) { - if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) { + if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) { + lastTypingCommand->setIsAutocompletion(options & IsAutocompletion); + lastTypingCommand->setCompositionType(TextCompositionNone); lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); - lastTypingCommand->insertLineBreak(); + lastTypingCommand->insertLineBreakAndNotifyAccessibility(); return; } - applyCommand(TypingCommand::create(document, InsertLineBreak, "", options)); + applyCommand(TypingCommand::create(document, InsertLineBreak, emptyString(), options)); } void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document) { - if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) { - lastTypingCommand->insertParagraphSeparatorInQuotedContent(); + if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) { + lastTypingCommand->setIsAutocompletion(false); + lastTypingCommand->setCompositionType(TextCompositionNone); + lastTypingCommand->insertParagraphSeparatorInQuotedContentAndNotifyAccessibility(); return; } @@ -210,47 +296,73 @@ void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document) void TypingCommand::insertParagraphSeparator(Document& document, Options options) { - if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) { + if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) { + lastTypingCommand->setIsAutocompletion(options & IsAutocompletion); + lastTypingCommand->setCompositionType(TextCompositionNone); lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); - lastTypingCommand->insertParagraphSeparator(); + lastTypingCommand->insertParagraphSeparatorAndNotifyAccessibility(); return; } - applyCommand(TypingCommand::create(document, InsertParagraphSeparator, "", options)); + applyCommand(TypingCommand::create(document, InsertParagraphSeparator, emptyString(), options)); } -PassRefPtr<TypingCommand> TypingCommand::lastTypingCommandIfStillOpenForTyping(Frame* frame) +RefPtr<TypingCommand> TypingCommand::lastTypingCommandIfStillOpenForTyping(Frame& frame) { - ASSERT(frame); - - RefPtr<CompositeEditCommand> lastEditCommand = frame->editor().lastEditCommand(); + RefPtr<CompositeEditCommand> lastEditCommand = frame.editor().lastEditCommand(); if (!lastEditCommand || !lastEditCommand->isTypingCommand() || !static_cast<TypingCommand*>(lastEditCommand.get())->isOpenForMoreTyping()) - return 0; + return nullptr; return static_cast<TypingCommand*>(lastEditCommand.get()); } +bool TypingCommand::shouldDeferWillApplyCommandUntilAddingTypingCommand() const +{ + return !m_isHandlingInitialTypingCommand || editActionIsDeleteByTyping(editingAction()); +} + void TypingCommand::closeTyping(Frame* frame) { - if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) + if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) lastTypingCommand->closeTyping(); } #if PLATFORM(IOS) void TypingCommand::ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping(Frame* frame, const VisibleSelection& newSelection) { - if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) { + if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) { lastTypingCommand->setEndingSelection(newSelection); lastTypingCommand->setEndingSelectionOnLastInsertCommand(newSelection); } } #endif +void TypingCommand::postTextStateChangeNotificationForDeletion(const VisibleSelection& selection) +{ + if (!AXObjectCache::accessibilityEnabled()) + return; + postTextStateChangeNotification(AXTextEditTypeDelete, AccessibilityObject::stringForVisiblePositionRange(selection), selection.start()); + VisiblePositionIndexRange range; + range.startIndex.value = indexForVisiblePosition(selection.start(), range.startIndex.scope); + range.endIndex.value = indexForVisiblePosition(selection.end(), range.endIndex.scope); + composition()->setRangeDeletedByUnapply(range); +} + +bool TypingCommand::willApplyCommand() +{ + if (shouldDeferWillApplyCommandUntilAddingTypingCommand()) { + // The TypingCommand will handle the willApplyCommand logic separately in TypingCommand::willAddTypingToOpenCommand. + return true; + } + + return CompositeEditCommand::willApplyCommand(); +} + void TypingCommand::doApply() { - if (!endingSelection().isNonOrphanedCaretOrRange()) + if (endingSelection().isNoneOrOrphaned()) return; - + if (m_commandType == DeleteKey) if (m_commands.isEmpty()) m_openedByBackwardDelete = true; @@ -260,44 +372,81 @@ void TypingCommand::doApply() deleteSelection(m_smartDelete); return; case DeleteKey: - deleteKeyPressed(m_granularity, m_killRing); + deleteKeyPressed(m_granularity, m_shouldAddToKillRing); return; case ForwardDeleteKey: - forwardDeleteKeyPressed(m_granularity, m_killRing); + forwardDeleteKeyPressed(m_granularity, m_shouldAddToKillRing); return; case InsertLineBreak: - insertLineBreak(); + insertLineBreakAndNotifyAccessibility(); return; case InsertParagraphSeparator: - insertParagraphSeparator(); + insertParagraphSeparatorAndNotifyAccessibility(); return; case InsertParagraphSeparatorInQuotedContent: - insertParagraphSeparatorInQuotedContent(); + insertParagraphSeparatorInQuotedContentAndNotifyAccessibility(); return; case InsertText: - insertText(m_textToInsert, m_selectInsertedText); + insertTextAndNotifyAccessibility(m_textToInsert, m_selectInsertedText); return; } ASSERT_NOT_REACHED(); } -EditAction TypingCommand::editingAction() const +String TypingCommand::inputEventTypeName() const { - return EditActionTyping; + return inputTypeNameForEditingAction(m_currentTypingEditAction); +} + +bool TypingCommand::isBeforeInputEventCancelable() const +{ + return m_currentTypingEditAction != EditActionTypingInsertPendingComposition && m_currentTypingEditAction != EditActionTypingDeletePendingComposition; +} + +String TypingCommand::inputEventData() const +{ + switch (m_currentTypingEditAction) { + case EditActionTypingInsertText: + case EditActionTypingInsertPendingComposition: + case EditActionTypingInsertFinalComposition: + return m_currentTextToInsert; + case EditActionInsertReplacement: + return isEditingTextAreaOrTextInput() ? m_currentTextToInsert : String(); + default: + return CompositeEditCommand::inputEventData(); + } +} + +RefPtr<DataTransfer> TypingCommand::inputEventDataTransfer() const +{ + if (m_currentTypingEditAction != EditActionInsertReplacement || isEditingTextAreaOrTextInput()) + return nullptr; + + StringBuilder htmlText; + MarkupAccumulator::appendCharactersReplacingEntities(htmlText, m_currentTextToInsert, 0, m_currentTextToInsert.length(), EntityMaskInHTMLPCDATA); + return DataTransfer::createForInputEvent(m_currentTextToInsert, htmlText.toString()); +} + +void TypingCommand::didApplyCommand() +{ + // TypingCommands handle applied editing separately (see TypingCommand::typingAddedToOpenCommand). + m_isHandlingInitialTypingCommand = false; } void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) { Frame& frame = this->frame(); -#if PLATFORM(MAC) && !PLATFORM(IOS) +#if PLATFORM(MAC) if (!frame.editor().isContinuousSpellCheckingEnabled() && !frame.editor().isAutomaticQuoteSubstitutionEnabled() && !frame.editor().isAutomaticLinkDetectionEnabled() && !frame.editor().isAutomaticDashSubstitutionEnabled() && !frame.editor().isAutomaticTextReplacementEnabled()) return; + if (frame.editor().isHandlingAcceptedCandidate()) + return; #else if (!frame.editor().isContinuousSpellCheckingEnabled()) return; @@ -339,13 +488,28 @@ void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) } } +bool TypingCommand::willAddTypingToOpenCommand(ETypingCommand commandType, TextGranularity granularity, const String& text, RefPtr<Range>&& range) +{ + m_currentTextToInsert = text; + m_currentTypingEditAction = editActionForTypingCommand(commandType, granularity, m_compositionType, m_isAutocompletion); + + if (!shouldDeferWillApplyCommandUntilAddingTypingCommand()) + return true; + + if (!range || isEditingTextAreaOrTextInput()) + return frame().editor().willApplyEditing(*this, CompositeEditCommand::targetRangesForBindings()); + + RefPtr<StaticRange> staticRange = StaticRange::createFromRange(*range); + return frame().editor().willApplyEditing(*this, { 1, staticRange }); +} + void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping) { Frame& frame = this->frame(); updatePreservesTypingStyle(commandTypeForAddedTyping); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) frame.editor().appliedEditing(this); // Since the spellchecking code may also perform corrections and other replacements, it should happen after the typing changes. if (!m_shouldPreventSpellChecking) @@ -368,10 +532,23 @@ void TypingCommand::insertText(const String &text, bool selectInsertedText) forEachLineInString(text, operation); } +void TypingCommand::insertTextAndNotifyAccessibility(const String &text, bool selectInsertedText) +{ + LOG(Editing, "TypingCommand %p insertTextAndNotifyAccessibility (text %s, selectInsertedText %d)", this, text.utf8().data(), selectInsertedText); + + AccessibilityReplacedText replacedText(frame().selection().selection()); + insertText(text, selectInsertedText); + replacedText.postTextStateChangeNotification(document().existingAXObjectCache(), AXTextEditTypeTyping, text, frame().selection().selection()); + composition()->setRangeDeletedByUnapply(replacedText.replacedRange()); +} + void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText) { + if (!willAddTypingToOpenCommand(InsertText, CharacterGranularity, text)) + return; + RefPtr<InsertTextCommand> command = InsertTextCommand::create(document(), text, selectInsertedText, - m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces); + m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces, EditActionTypingInsertText); applyCommandToComposite(command, endingSelection()); @@ -383,21 +560,46 @@ void TypingCommand::insertLineBreak() if (!canAppendNewLineFeedToSelection(endingSelection())) return; + if (!willAddTypingToOpenCommand(InsertLineBreak, LineGranularity)) + return; + applyCommandToComposite(InsertLineBreakCommand::create(document())); typingAddedToOpenCommand(InsertLineBreak); } +void TypingCommand::insertLineBreakAndNotifyAccessibility() +{ + AccessibilityReplacedText replacedText(frame().selection().selection()); + insertLineBreak(); + replacedText.postTextStateChangeNotification(document().existingAXObjectCache(), AXTextEditTypeTyping, "\n", frame().selection().selection()); + composition()->setRangeDeletedByUnapply(replacedText.replacedRange()); +} + void TypingCommand::insertParagraphSeparator() { if (!canAppendNewLineFeedToSelection(endingSelection())) return; - applyCommandToComposite(InsertParagraphSeparatorCommand::create(document())); + if (!willAddTypingToOpenCommand(InsertParagraphSeparator, ParagraphGranularity)) + return; + + applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), false, false, EditActionTypingInsertParagraph)); typingAddedToOpenCommand(InsertParagraphSeparator); } +void TypingCommand::insertParagraphSeparatorAndNotifyAccessibility() +{ + AccessibilityReplacedText replacedText(frame().selection().selection()); + insertParagraphSeparator(); + replacedText.postTextStateChangeNotification(document().existingAXObjectCache(), AXTextEditTypeTyping, "\n", frame().selection().selection()); + composition()->setRangeDeletedByUnapply(replacedText.replacedRange()); +} + void TypingCommand::insertParagraphSeparatorInQuotedContent() { + if (!willAddTypingToOpenCommand(InsertParagraphSeparatorInQuotedContent, ParagraphGranularity)) + return; + // If the selection starts inside a table, just insert the paragraph separator normally // Breaking the blockquote would also break apart the table, which is unecessary when inserting a newline if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) { @@ -409,6 +611,14 @@ void TypingCommand::insertParagraphSeparatorInQuotedContent() typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent); } +void TypingCommand::insertParagraphSeparatorInQuotedContentAndNotifyAccessibility() +{ + AccessibilityReplacedText replacedText(frame().selection().selection()); + insertParagraphSeparatorInQuotedContent(); + replacedText.postTextStateChangeNotification(document().existingAXObjectCache(), AXTextEditTypeTyping, "\n", frame().selection().selection()); + composition()->setRangeDeletedByUnapply(replacedText.replacedRange()); +} + bool TypingCommand::makeEditableRootEmpty() { Element* root = endingSelection().rootEditableElement(); @@ -430,9 +640,10 @@ bool TypingCommand::makeEditableRootEmpty() return true; } -void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) +void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool shouldAddToKillRing) { Frame& frame = this->frame(); + Ref<Frame> protector(frame); frame.editor().updateMarkersForWordsAffectedByEditing(false); @@ -455,16 +666,20 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) FrameSelection selection; selection.setSelection(endingSelection()); selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity); - if (killRing && selection.isCaret() && granularity != CharacterGranularity) + if (shouldAddToKillRing && selection.isCaret() && granularity != CharacterGranularity) selection.modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity); if (endingSelection().visibleStart().previous(CannotCrossEditingBoundary).isNull()) { // When the caret is at the start of the editable area in an empty list item, break out of the list item. - if (breakOutOfEmptyListItem()) { - typingAddedToOpenCommand(DeleteKey); + if (auto deleteListSelection = shouldBreakOutOfEmptyListItem()) { + if (willAddTypingToOpenCommand(DeleteKey, granularity, { }, Range::create(document(), deleteListSelection.value().start(), deleteListSelection.value().end()))) { + breakOutOfEmptyListItem(); + typingAddedToOpenCommand(DeleteKey); + } return; } // When there are no visible positions in the editing root, delete its entire contents. + // FIXME: Dispatch a `beforeinput` event here and bail if preventDefault() was invoked. if (endingSelection().visibleStart().next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) { typingAddedToOpenCommand(DeleteKey); return; @@ -527,8 +742,15 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) if (selectionToDelete.isCaret() || !frame.selection().shouldDeleteSelection(selectionToDelete)) return; - if (killRing) - frame.editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false); + if (!willAddTypingToOpenCommand(DeleteKey, granularity, { }, selectionToDelete.firstRange())) + return; + + if (shouldAddToKillRing) + frame.editor().addRangeToKillRing(*selectionToDelete.toNormalizedRange().get(), Editor::KillRingInsertionMode::PrependText); + + // Post the accessibility notification before actually deleting the content while selectionToDelete is still valid + postTextStateChangeNotificationForDeletion(selectionToDelete); + // Make undo select everything that has been deleted, unless an undo will undo more than just this deletion. // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete // more text than you insert. In that case all of the text that was around originally should be selected. @@ -539,9 +761,10 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) typingAddedToOpenCommand(DeleteKey); } -void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing) +void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool shouldAddToKillRing) { Frame& frame = this->frame(); + Ref<Frame> protector(frame); frame.editor().updateMarkersForWordsAffectedByEditing(false); @@ -562,7 +785,7 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki FrameSelection selection; selection.setSelection(endingSelection()); selection.modify(FrameSelection::AlterationExtend, DirectionForward, granularity); - if (killRing && selection.isCaret() && granularity != CharacterGranularity) + if (shouldAddToKillRing && selection.isCaret() && granularity != CharacterGranularity) selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity); Position downstreamEnd = endingSelection().end().downstream(); @@ -574,7 +797,7 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream(); // When deleting tables: Select the table first, then perform the deletion if (downstreamEnd.containerNode() && downstreamEnd.containerNode()->renderer() && downstreamEnd.containerNode()->renderer()->isTable() - && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) { + && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(*downstreamEnd.containerNode())) { setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional())); typingAddedToOpenCommand(ForwardDeleteKey); return; @@ -624,9 +847,15 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki if (selectionToDelete.isCaret() || !frame.selection().shouldDeleteSelection(selectionToDelete)) return; - - if (killRing) - frame.editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false); + + if (!willAddTypingToOpenCommand(ForwardDeleteKey, granularity, { }, selectionToDelete.firstRange())) + return; + + // Post the accessibility notification before actually deleting the content while selectionToDelete is still valid + postTextStateChangeNotificationForDeletion(selectionToDelete); + + if (shouldAddToKillRing) + frame.editor().addRangeToKillRing(*selectionToDelete.toNormalizedRange().get(), Editor::KillRingInsertionMode::AppendText); // make undo select what was deleted setStartingSelection(selectionAfterUndo); CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); @@ -636,6 +865,9 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki void TypingCommand::deleteSelection(bool smartDelete) { + if (!willAddTypingToOpenCommand(DeleteSelection, CharacterGranularity)) + return; + CompositeEditCommand::deleteSelection(smartDelete); typingAddedToOpenCommand(DeleteSelection); } diff --git a/Source/WebCore/editing/TypingCommand.h b/Source/WebCore/editing/TypingCommand.h index e249a34b1..44666884b 100644 --- a/Source/WebCore/editing/TypingCommand.h +++ b/Source/WebCore/editing/TypingCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,14 +23,13 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TypingCommand_h -#define TypingCommand_h +#pragma once #include "TextInsertionBaseCommand.h" namespace WebCore { -class TypingCommand : public TextInsertionBaseCommand { +class TypingCommand final : public TextInsertionBaseCommand { public: enum ETypingCommand { DeleteSelection, @@ -44,20 +43,21 @@ public: enum TextCompositionType { TextCompositionNone, - TextCompositionUpdate, - TextCompositionConfirm + TextCompositionPending, + TextCompositionFinal, }; enum Option { SelectInsertedText = 1 << 0, - KillRing = 1 << 1, + AddsToKillRing = 1 << 1, RetainAutocorrectionIndicator = 1 << 2, PreventSpellChecking = 1 << 3, - SmartDelete = 1 << 4 + SmartDelete = 1 << 4, + IsAutocompletion = 1 << 5, }; typedef unsigned Options; - static void deleteSelection(Document&, Options = 0); + static void deleteSelection(Document&, Options = 0, TextCompositionType = TextCompositionNone); static void deleteKeyPressed(Document&, Options = 0, TextGranularity = CharacterGranularity); static void forwardDeleteKeyPressed(Document&, Options = 0, TextGranularity = CharacterGranularity); static void insertText(Document&, const String&, Options, TextCompositionType = TextCompositionNone); @@ -75,24 +75,25 @@ public: void insertLineBreak(); void insertParagraphSeparatorInQuotedContent(); void insertParagraphSeparator(); - void deleteKeyPressed(TextGranularity, bool killRing); - void forwardDeleteKeyPressed(TextGranularity, bool killRing); + void deleteKeyPressed(TextGranularity, bool shouldAddToKillRing); + void forwardDeleteKeyPressed(TextGranularity, bool shouldAddToKillRing); void deleteSelection(bool smartDelete); void setCompositionType(TextCompositionType type) { m_compositionType = type; } + void setIsAutocompletion(bool isAutocompletion) { m_isAutocompletion = isAutocompletion; } #if PLATFORM(IOS) void setEndingSelectionOnLastInsertCommand(const VisibleSelection& selection); #endif private: - static PassRefPtr<TypingCommand> create(Document& document, ETypingCommand command, const String& text = "", Options options = 0, TextGranularity granularity = CharacterGranularity) + static Ref<TypingCommand> create(Document& document, ETypingCommand command, const String& text = emptyString(), Options options = 0, TextGranularity granularity = CharacterGranularity, TextCompositionType compositionType = TextCompositionNone) { - return adoptRef(new TypingCommand(document, command, text, options, granularity, TextCompositionNone)); + return adoptRef(*new TypingCommand(document, command, text, options, granularity, compositionType)); } - static PassRefPtr<TypingCommand> create(Document& document, ETypingCommand command, const String& text, Options options, TextCompositionType compositionType) + static Ref<TypingCommand> create(Document& document, ETypingCommand command, const String& text, Options options, TextCompositionType compositionType) { - return adoptRef(new TypingCommand(document, command, text, options, CharacterGranularity, compositionType)); + return adoptRef(*new TypingCommand(document, command, text, options, CharacterGranularity, compositionType)); } TypingCommand(Document&, ETypingCommand, const String& text, Options, TextGranularity, TextCompositionType); @@ -102,37 +103,57 @@ private: bool isOpenForMoreTyping() const { return m_openForMoreTyping; } void closeTyping() { m_openForMoreTyping = false; } - static PassRefPtr<TypingCommand> lastTypingCommandIfStillOpenForTyping(Frame*); + static RefPtr<TypingCommand> lastTypingCommandIfStillOpenForTyping(Frame&); - virtual void doApply(); - virtual EditAction editingAction() const; - virtual bool isTypingCommand() const; - virtual bool preservesTypingStyle() const { return m_preservesTypingStyle; } - virtual bool shouldRetainAutocorrectionIndicator() const + void doApply() final; + bool isTypingCommand() const final; + bool preservesTypingStyle() const final { return m_preservesTypingStyle; } + bool shouldRetainAutocorrectionIndicator() const final { ASSERT(isTopLevelCommand()); return m_shouldRetainAutocorrectionIndicator; } - virtual void setShouldRetainAutocorrectionIndicator(bool retain) { m_shouldRetainAutocorrectionIndicator = retain; } - virtual bool shouldStopCaretBlinking() const { return true; } + void setShouldRetainAutocorrectionIndicator(bool retain) final { m_shouldRetainAutocorrectionIndicator = retain; } + bool shouldStopCaretBlinking() const final { return true; } void setShouldPreventSpellChecking(bool prevent) { m_shouldPreventSpellChecking = prevent; } + String inputEventTypeName() const final; + String inputEventData() const final; + RefPtr<DataTransfer> inputEventDataTransfer() const final; + bool isBeforeInputEventCancelable() const final; + static void updateSelectionIfDifferentFromCurrentSelection(TypingCommand*, Frame*); void updatePreservesTypingStyle(ETypingCommand); + bool willAddTypingToOpenCommand(ETypingCommand, TextGranularity, const String& = emptyString(), RefPtr<Range>&& = nullptr); void markMisspellingsAfterTyping(ETypingCommand); void typingAddedToOpenCommand(ETypingCommand); bool makeEditableRootEmpty(); + void postTextStateChangeNotificationForDeletion(const VisibleSelection&); + void insertTextAndNotifyAccessibility(const String &text, bool selectInsertedText); + void insertLineBreakAndNotifyAccessibility(); + void insertParagraphSeparatorInQuotedContentAndNotifyAccessibility(); + void insertParagraphSeparatorAndNotifyAccessibility(); + + bool willApplyCommand() final; + void didApplyCommand() final; + + bool shouldDeferWillApplyCommandUntilAddingTypingCommand() const; + ETypingCommand m_commandType; + EditAction m_currentTypingEditAction; String m_textToInsert; + String m_currentTextToInsert; bool m_openForMoreTyping; bool m_selectInsertedText; bool m_smartDelete; + bool m_isHandlingInitialTypingCommand { true }; TextGranularity m_granularity; TextCompositionType m_compositionType; - bool m_killRing; + bool m_shouldAddToKillRing; bool m_preservesTypingStyle; + bool m_isAutocompletion; // Undoing a series of backward deletes will restore a selection around all of the // characters that were deleted, but only if the typing command being undone @@ -144,5 +165,3 @@ private: }; } // namespace WebCore - -#endif // TypingCommand_h diff --git a/Source/WebCore/editing/UndoStep.h b/Source/WebCore/editing/UndoStep.h index ed202b427..ff10f2799 100644 --- a/Source/WebCore/editing/UndoStep.h +++ b/Source/WebCore/editing/UndoStep.h @@ -28,8 +28,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef UndoStep_h -#define UndoStep_h +#pragma once #include "EditAction.h" #include <wtf/RefCounted.h> @@ -45,6 +44,4 @@ public: virtual EditAction editingAction() const = 0; }; -} - -#endif +} // namespace WebCore diff --git a/Source/WebCore/editing/UnlinkCommand.cpp b/Source/WebCore/editing/UnlinkCommand.cpp index a82f2a0e0..443e9a667 100644 --- a/Source/WebCore/editing/UnlinkCommand.cpp +++ b/Source/WebCore/editing/UnlinkCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2006 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 diff --git a/Source/WebCore/editing/UnlinkCommand.h b/Source/WebCore/editing/UnlinkCommand.h index 47c62c137..17c50a9a3 100644 --- a/Source/WebCore/editing/UnlinkCommand.h +++ b/Source/WebCore/editing/UnlinkCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef UnlinkCommand_h -#define UnlinkCommand_h +#pragma once #include "CompositeEditCommand.h" @@ -32,18 +31,16 @@ namespace WebCore { class UnlinkCommand : public CompositeEditCommand { public: - static PassRefPtr<UnlinkCommand> create(Document& document) + static Ref<UnlinkCommand> create(Document& document) { - return adoptRef(new UnlinkCommand(document)); + return adoptRef(*new UnlinkCommand(document)); } private: explicit UnlinkCommand(Document&); - virtual void doApply(); - virtual EditAction editingAction() const { return EditActionUnlink; } + void doApply() override; + EditAction editingAction() const override { return EditActionUnlink; } }; } // namespace WebCore - -#endif // UnlinkCommand_h diff --git a/Source/WebCore/editing/VisiblePosition.cpp b/Source/WebCore/editing/VisiblePosition.cpp index 9155aaef3..bda4a1476 100644 --- a/Source/WebCore/editing/VisiblePosition.cpp +++ b/Source/WebCore/editing/VisiblePosition.cpp @@ -11,10 +11,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -30,6 +30,7 @@ #include "Document.h" #include "FloatQuad.h" #include "HTMLElement.h" +#include "HTMLHtmlElement.h" #include "HTMLNames.h" #include "InlineTextBox.h" #include "Logging.h" @@ -37,6 +38,7 @@ #include "RenderBlock.h" #include "RootInlineBox.h" #include "Text.h" +#include "TextStream.h" #include "VisibleUnits.h" #include "htmlediting.h" #include <stdio.h> @@ -62,8 +64,10 @@ void VisiblePosition::init(const Position& position, EAffinity affinity) m_affinity = DOWNSTREAM; } -VisiblePosition VisiblePosition::next(EditingBoundaryCrossingRule rule) const +VisiblePosition VisiblePosition::next(EditingBoundaryCrossingRule rule, bool* reachedBoundary) const { + if (reachedBoundary) + *reachedBoundary = false; // FIXME: Support CanSkipEditingBoundary ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary); VisiblePosition next(nextVisuallyDistinctCandidate(m_deepPosition), m_affinity); @@ -71,19 +75,24 @@ VisiblePosition VisiblePosition::next(EditingBoundaryCrossingRule rule) const if (rule == CanCrossEditingBoundary) return next; - return honorEditingBoundaryAtOrAfter(next); + return honorEditingBoundaryAtOrAfter(next, reachedBoundary); } -VisiblePosition VisiblePosition::previous(EditingBoundaryCrossingRule rule) const +VisiblePosition VisiblePosition::previous(EditingBoundaryCrossingRule rule, bool* reachedBoundary) const { + if (reachedBoundary) + *reachedBoundary = false; // FIXME: Support CanSkipEditingBoundary ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary); // find first previous DOM position that is visible Position pos = previousVisuallyDistinctCandidate(m_deepPosition); // return null visible position if there is no previous visible position - if (pos.atStartOfTree()) + if (pos.atStartOfTree()) { + if (reachedBoundary) + *reachedBoundary = true; return VisiblePosition(); + } VisiblePosition prev = VisiblePosition(pos, DOWNSTREAM); ASSERT(prev != *this); @@ -101,7 +110,7 @@ VisiblePosition VisiblePosition::previous(EditingBoundaryCrossingRule rule) cons if (rule == CanCrossEditingBoundary) return prev; - return honorEditingBoundaryAtOrBefore(prev); + return honorEditingBoundaryAtOrBefore(prev, reachedBoundary); } Position VisiblePosition::leftVisuallyDistinctCandidate() const @@ -173,7 +182,7 @@ Position VisiblePosition::leftVisuallyDistinctCandidate() const if (box->direction() == primaryDirection) { if (!prevBox) { - InlineBox* logicalStart = 0; + InlineBox* logicalStart = nullptr; if (primaryDirection == LTR ? box->root().getLogicalStartBoxWithNode(logicalStart) : box->root().getLogicalEndBoxWithNode(logicalStart)) { box = logicalStart; renderer = &box->renderer(); @@ -252,12 +261,17 @@ Position VisiblePosition::leftVisuallyDistinctCandidate() const } } -VisiblePosition VisiblePosition::left(bool stayInEditableContent) const +VisiblePosition VisiblePosition::left(bool stayInEditableContent, bool* reachedBoundary) const { + if (reachedBoundary) + *reachedBoundary = false; Position pos = leftVisuallyDistinctCandidate(); // FIXME: Why can't we move left from the last position in a tree? - if (pos.atStartOfTree() || pos.atEndOfTree()) + if (pos.atStartOfTree() || pos.atEndOfTree()) { + if (reachedBoundary) + *reachedBoundary = true; return VisiblePosition(); + } VisiblePosition left = VisiblePosition(pos, DOWNSTREAM); ASSERT(left != *this); @@ -266,7 +280,7 @@ VisiblePosition VisiblePosition::left(bool stayInEditableContent) const return left; // FIXME: This may need to do something different from "before". - return honorEditingBoundaryAtOrBefore(left); + return honorEditingBoundaryAtOrBefore(left, reachedBoundary); } Position VisiblePosition::rightVisuallyDistinctCandidate() const @@ -338,7 +352,7 @@ Position VisiblePosition::rightVisuallyDistinctCandidate() const if (box->direction() == primaryDirection) { if (!nextBox) { - InlineBox* logicalEnd = 0; + InlineBox* logicalEnd = nullptr; if (primaryDirection == LTR ? box->root().getLogicalEndBoxWithNode(logicalEnd) : box->root().getLogicalStartBoxWithNode(logicalEnd)) { box = logicalEnd; renderer = &box->renderer(); @@ -420,12 +434,17 @@ Position VisiblePosition::rightVisuallyDistinctCandidate() const } } -VisiblePosition VisiblePosition::right(bool stayInEditableContent) const +VisiblePosition VisiblePosition::right(bool stayInEditableContent, bool* reachedBoundary) const { + if (reachedBoundary) + *reachedBoundary = false; Position pos = rightVisuallyDistinctCandidate(); // FIXME: Why can't we move left from the last position in a tree? - if (pos.atStartOfTree() || pos.atEndOfTree()) + if (pos.atStartOfTree() || pos.atEndOfTree()) { + if (reachedBoundary) + *reachedBoundary = true; return VisiblePosition(); + } VisiblePosition right = VisiblePosition(pos, DOWNSTREAM); ASSERT(right != *this); @@ -434,56 +453,78 @@ VisiblePosition VisiblePosition::right(bool stayInEditableContent) const return right; // FIXME: This may need to do something different from "after". - return honorEditingBoundaryAtOrAfter(right); + return honorEditingBoundaryAtOrAfter(right, reachedBoundary); } -VisiblePosition VisiblePosition::honorEditingBoundaryAtOrBefore(const VisiblePosition &pos) const +VisiblePosition VisiblePosition::honorEditingBoundaryAtOrBefore(const VisiblePosition& position, bool* reachedBoundary) const { - if (pos.isNull()) - return pos; + if (reachedBoundary) + *reachedBoundary = false; + if (position.isNull()) + return position; - Node* highestRoot = highestEditableRoot(deepEquivalent()); + auto* highestRoot = highestEditableRoot(deepEquivalent()); // Return empty position if pos is not somewhere inside the editable region containing this position - if (highestRoot && !pos.deepEquivalent().deprecatedNode()->isDescendantOf(highestRoot)) + if (highestRoot && !position.deepEquivalent().deprecatedNode()->isDescendantOf(*highestRoot)) { + if (reachedBoundary) + *reachedBoundary = true; return VisiblePosition(); - - // Return pos itself if the two are from the very same editable region, or both are non-editable + } + + // Return position itself if the two are from the very same editable region, or both are non-editable // FIXME: In the non-editable case, just because the new position is non-editable doesn't mean movement // to it is allowed. VisibleSelection::adjustForEditableContent has this problem too. - if (highestEditableRoot(pos.deepEquivalent()) == highestRoot) - return pos; + if (highestEditableRoot(position.deepEquivalent()) == highestRoot) { + if (reachedBoundary) + *reachedBoundary = *this == position; + return position; + } // Return empty position if this position is non-editable, but pos is editable // FIXME: Move to the previous non-editable region. - if (!highestRoot) + if (!highestRoot) { + if (reachedBoundary) + *reachedBoundary = true; return VisiblePosition(); + } // Return the last position before pos that is in the same editable region as this position - return lastEditablePositionBeforePositionInRoot(pos.deepEquivalent(), highestRoot); + return lastEditablePositionBeforePositionInRoot(position.deepEquivalent(), highestRoot); } -VisiblePosition VisiblePosition::honorEditingBoundaryAtOrAfter(const VisiblePosition &pos) const +VisiblePosition VisiblePosition::honorEditingBoundaryAtOrAfter(const VisiblePosition &pos, bool* reachedBoundary) const { + if (reachedBoundary) + *reachedBoundary = false; if (pos.isNull()) return pos; - Node* highestRoot = highestEditableRoot(deepEquivalent()); + auto* highestRoot = highestEditableRoot(deepEquivalent()); // Return empty position if pos is not somewhere inside the editable region containing this position - if (highestRoot && !pos.deepEquivalent().deprecatedNode()->isDescendantOf(highestRoot)) + if (highestRoot && !pos.deepEquivalent().deprecatedNode()->isDescendantOf(*highestRoot)) { + if (reachedBoundary) + *reachedBoundary = true; return VisiblePosition(); + } // Return pos itself if the two are from the very same editable region, or both are non-editable // FIXME: In the non-editable case, just because the new position is non-editable doesn't mean movement // to it is allowed. VisibleSelection::adjustForEditableContent has this problem too. - if (highestEditableRoot(pos.deepEquivalent()) == highestRoot) + if (highestEditableRoot(pos.deepEquivalent()) == highestRoot) { + if (reachedBoundary) + *reachedBoundary = *this == pos; return pos; + } // Return empty position if this position is non-editable, but pos is editable // FIXME: Move to the next non-editable region. - if (!highestRoot) + if (!highestRoot) { + if (reachedBoundary) + *reachedBoundary = true; return VisiblePosition(); + } // Return the next position after pos that is in the same editable region as this position return firstEditablePositionAfterPositionInRoot(pos.deepEquivalent(), highestRoot); @@ -536,14 +577,17 @@ Position VisiblePosition::canonicalPosition(const Position& passedPosition) // The new position must be in the same editable element. Enforce that first. // Unless the descent is from a non-editable html element to an editable body. - if (node && node->hasTagName(htmlTag) && !node->hasEditableStyle() && node->document().body() && node->document().body()->hasEditableStyle()) - return next.isNotNull() ? next : prev; + if (is<HTMLHtmlElement>(node) && !node->hasEditableStyle()) { + auto* body = node->document().bodyOrFrameset(); + if (body && body->hasEditableStyle()) + return next.isNotNull() ? next : prev; + } Node* editingRoot = editableRootForPosition(position); // If the html element is editable, descending into its body will look like a descent // from non-editable to editable content since rootEditableElement() always stops at the body. - if ((editingRoot && editingRoot->hasTagName(htmlTag)) || position.deprecatedNode()->isDocumentNode()) + if ((editingRoot && editingRoot->hasTagName(htmlTag)) || (node && (node->isDocumentNode() || node->isShadowRoot()))) return next.isNotNull() ? next : prev; bool prevIsInSameEditableElement = prevNode && editableRootForPosition(prev) == editingRoot; @@ -590,15 +634,14 @@ UChar32 VisiblePosition::characterAfter() const return 0; UChar32 ch; - const UChar* characters = textNode->data().deprecatedCharacters(); - U16_NEXT(characters, offset, length, ch); + U16_NEXT(textNode->data(), offset, length, ch); return ch; } LayoutRect VisiblePosition::localCaretRect(RenderObject*& renderer) const { if (m_deepPosition.isNull()) { - renderer = 0; + renderer = nullptr; return IntRect(); } Node* node = m_deepPosition.anchorNode(); @@ -617,14 +660,11 @@ LayoutRect VisiblePosition::localCaretRect(RenderObject*& renderer) const return renderer->localCaretRect(inlineBox, caretOffset); } -IntRect VisiblePosition::absoluteCaretBounds() const +IntRect VisiblePosition::absoluteCaretBounds(bool* insideFixed) const { - RenderObject* renderer; - LayoutRect localRect = localCaretRect(renderer); - if (localRect.isEmpty() || !renderer) - return IntRect(); - - return renderer->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox(); + RenderBlock* renderer = nullptr; + LayoutRect localRect = localCaretRectInRendererForCaretPainting(*this, renderer); + return absoluteBoundsForLocalCaretRect(renderer, localRect, insideFixed); } int VisiblePosition::lineDirectionPointForBlockDirectionNavigation() const @@ -644,7 +684,7 @@ int VisiblePosition::lineDirectionPointForBlockDirectionNavigation() const return containingBlock->isHorizontalWritingMode() ? caretPoint.x() : caretPoint.y(); } -#ifndef NDEBUG +#if ENABLE(TREE_DEBUGGING) void VisiblePosition::debugPosition(const char* msg) const { @@ -668,15 +708,15 @@ void VisiblePosition::showTreeForThis() const #endif -PassRefPtr<Range> makeRange(const VisiblePosition &start, const VisiblePosition &end) +RefPtr<Range> makeRange(const VisiblePosition& start, const VisiblePosition& end) { if (start.isNull() || end.isNull()) - return 0; + return nullptr; Position s = start.deepEquivalent().parentAnchoredEquivalent(); Position e = end.deepEquivalent().parentAnchoredEquivalent(); if (s.isNull() || e.isNull()) - return 0; + return nullptr; return Range::create(s.containerNode()->document(), s.containerNode(), s.offsetInContainerNode(), e.containerNode(), e.offsetInContainerNode()); } @@ -691,31 +731,35 @@ VisiblePosition endVisiblePosition(const Range *r, EAffinity affinity) return VisiblePosition(r->endPosition(), affinity); } -bool setStart(Range *r, const VisiblePosition &visiblePosition) +bool setStart(Range* range, const VisiblePosition& visiblePosition) { - if (!r) + if (!range) return false; + Position p = visiblePosition.deepEquivalent().parentAnchoredEquivalent(); - int code = 0; - r->setStart(p.containerNode(), p.offsetInContainerNode(), code); - return code == 0; + if (!p.containerNode()) + return false; + + return !range->setStart(*p.containerNode(), p.offsetInContainerNode()).hasException(); } -bool setEnd(Range *r, const VisiblePosition &visiblePosition) +bool setEnd(Range* range, const VisiblePosition& visiblePosition) { - if (!r) + if (!range) return false; + Position p = visiblePosition.deepEquivalent().parentAnchoredEquivalent(); - int code = 0; - r->setEnd(p.containerNode(), p.offsetInContainerNode(), code); - return code == 0; + if (!p.containerNode()) + return false; + + return !range->setEnd(*p.containerNode(), p.offsetInContainerNode()).hasException(); } // FIXME: Maybe this should be deprecated too, like the underlying function? Element* enclosingBlockFlowElement(const VisiblePosition& visiblePosition) { if (visiblePosition.isNull()) - return NULL; + return nullptr; return deprecatedEnclosingBlockFlowElement(visiblePosition.deepEquivalent().deprecatedNode()); } @@ -744,9 +788,38 @@ bool isLastVisiblePositionInNode(const VisiblePosition &visiblePosition, const N return next.isNull() || !next.deepEquivalent().deprecatedNode()->isDescendantOf(node); } +bool VisiblePosition::equals(const VisiblePosition& other) const +{ + return m_affinity == other.m_affinity && m_deepPosition.equals(other.m_deepPosition); +} + +TextStream& operator<<(TextStream& stream, EAffinity affinity) +{ + switch (affinity) { + case UPSTREAM: + stream << "upstream"; + break; + case DOWNSTREAM: + stream << "downstream"; + break; + } + return stream; +} + +TextStream& operator<<(TextStream& stream, const VisiblePosition& visiblePosition) +{ + TextStream::GroupScope scope(stream); + stream << "VisiblePosition " << &visiblePosition; + + stream.dumpProperty("position", visiblePosition.deepEquivalent()); + stream.dumpProperty("affinity", visiblePosition.affinity()); + + return stream; +} + } // namespace WebCore -#ifndef NDEBUG +#if ENABLE(TREE_DEBUGGING) void showTree(const WebCore::VisiblePosition* vpos) { diff --git a/Source/WebCore/editing/VisiblePosition.h b/Source/WebCore/editing/VisiblePosition.h index 0ce78f444..3b9273538 100644 --- a/Source/WebCore/editing/VisiblePosition.h +++ b/Source/WebCore/editing/VisiblePosition.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,12 +23,11 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef VisiblePosition_h -#define VisiblePosition_h +#pragma once #include "EditingBoundary.h" #include "Position.h" -#include "TextDirection.h" +#include "TextFlags.h" namespace WebCore { @@ -47,13 +46,14 @@ namespace WebCore { class InlineBox; class Node; +class TextStream; class VisiblePosition { public: // NOTE: UPSTREAM affinity will be used only if pos is at end of a wrapped line, // otherwise it will be converted to DOWNSTREAM VisiblePosition() : m_affinity(VP_DEFAULT_AFFINITY) { } - VisiblePosition(const Position&, EAffinity = VP_DEFAULT_AFFINITY); + WEBCORE_EXPORT VisiblePosition(const Position&, EAffinity = VP_DEFAULT_AFFINITY); void clear() { m_deepPosition.clear(); } @@ -68,15 +68,15 @@ public: // FIXME: Change the following functions' parameter from a boolean to StayInEditableContent. // next() and previous() will increment/decrement by a character cluster. - VisiblePosition next(EditingBoundaryCrossingRule = CanCrossEditingBoundary) const; - VisiblePosition previous(EditingBoundaryCrossingRule = CanCrossEditingBoundary) const; - VisiblePosition honorEditingBoundaryAtOrBefore(const VisiblePosition&) const; - VisiblePosition honorEditingBoundaryAtOrAfter(const VisiblePosition&) const; + WEBCORE_EXPORT VisiblePosition next(EditingBoundaryCrossingRule = CanCrossEditingBoundary, bool* reachedBoundary = nullptr) const; + WEBCORE_EXPORT VisiblePosition previous(EditingBoundaryCrossingRule = CanCrossEditingBoundary, bool* reachedBoundary = nullptr) const; + VisiblePosition honorEditingBoundaryAtOrBefore(const VisiblePosition&, bool* reachedBoundary = nullptr) const; + VisiblePosition honorEditingBoundaryAtOrAfter(const VisiblePosition&, bool* reachedBoundary = nullptr) const; - VisiblePosition left(bool stayInEditableContent = false) const; - VisiblePosition right(bool stayInEditableContent = false) const; + WEBCORE_EXPORT VisiblePosition left(bool stayInEditableContent = false, bool* reachedBoundary = nullptr) const; + WEBCORE_EXPORT VisiblePosition right(bool stayInEditableContent = false, bool* reachedBoundary = nullptr) const; - UChar32 characterAfter() const; + WEBCORE_EXPORT UChar32 characterAfter() const; UChar32 characterBefore() const { return previous().characterAfter(); } // FIXME: This does not handle [table, 0] correctly. @@ -93,14 +93,18 @@ public: } // Rect is local to the returned renderer - LayoutRect localCaretRect(RenderObject*&) const; + WEBCORE_EXPORT LayoutRect localCaretRect(RenderObject*&) const; // Bounds of (possibly transformed) caret in absolute coords - IntRect absoluteCaretBounds() const; + WEBCORE_EXPORT IntRect absoluteCaretBounds(bool* insideFixed = nullptr) const; // Abs x/y position of the caret ignoring transforms. // FIXME: navigation with transforms should be smarter. - int lineDirectionPointForBlockDirectionNavigation() const; - -#ifndef NDEBUG + WEBCORE_EXPORT int lineDirectionPointForBlockDirectionNavigation() const; + + // This is a tentative enhancement of operator== to account for affinity. + // FIXME: Combine this function with operator== + bool equals(const VisiblePosition&) const; + +#if ENABLE(TREE_DEBUGGING) void debugPosition(const char* msg = "") const; void formatForDebugger(char* buffer, unsigned length) const; void showTreeForThis() const; @@ -128,7 +132,6 @@ inline bool operator!=(const VisiblePosition& a, const VisiblePosition& b) return !(a == b); } -#if PLATFORM(IOS) inline bool operator<(const VisiblePosition& a, const VisiblePosition& b) { return a.deepEquivalent() < b.deepEquivalent(); @@ -148,25 +151,25 @@ inline bool operator>=(const VisiblePosition& a, const VisiblePosition& b) { return a.deepEquivalent() >= b.deepEquivalent(); } -#endif -PassRefPtr<Range> makeRange(const VisiblePosition&, const VisiblePosition&); +WEBCORE_EXPORT RefPtr<Range> makeRange(const VisiblePosition&, const VisiblePosition&); bool setStart(Range*, const VisiblePosition&); bool setEnd(Range*, const VisiblePosition&); VisiblePosition startVisiblePosition(const Range*, EAffinity); VisiblePosition endVisiblePosition(const Range*, EAffinity); -Element* enclosingBlockFlowElement(const VisiblePosition&); +WEBCORE_EXPORT Element* enclosingBlockFlowElement(const VisiblePosition&); bool isFirstVisiblePositionInNode(const VisiblePosition&, const Node*); bool isLastVisiblePositionInNode(const VisiblePosition&, const Node*); +TextStream& operator<<(TextStream&, EAffinity); +TextStream& operator<<(TextStream&, const VisiblePosition&); + } // namespace WebCore -#ifndef NDEBUG -// Outside the WebCore namespace for ease of invocation from gdb. +#if ENABLE(TREE_DEBUGGING) +// Outside the WebCore namespace for ease of invocation from the debugger. void showTree(const WebCore::VisiblePosition*); void showTree(const WebCore::VisiblePosition&); #endif - -#endif // VisiblePosition_h diff --git a/Source/WebCore/editing/VisibleSelection.cpp b/Source/WebCore/editing/VisibleSelection.cpp index 79f61199d..60f51b64c 100644 --- a/Source/WebCore/editing/VisibleSelection.cpp +++ b/Source/WebCore/editing/VisibleSelection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2004, 2005, 2006, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -28,6 +28,7 @@ #include "Document.h" #include "Element.h" +#include "HTMLInputElement.h" #include "TextIterator.h" #include "VisibleUnits.h" #include "htmlediting.h" @@ -83,9 +84,9 @@ VisibleSelection::VisibleSelection(const VisiblePosition& base, const VisiblePos validate(); } -VisibleSelection::VisibleSelection(const Range* range, EAffinity affinity, bool isDirectional) - : m_base(range->startPosition()) - , m_extent(range->endPosition()) +VisibleSelection::VisibleSelection(const Range& range, EAffinity affinity, bool isDirectional) + : m_base(range.startPosition()) + , m_extent(range.endPosition()) , m_affinity(affinity) , m_isDirectional(isDirectional) { @@ -94,7 +95,7 @@ VisibleSelection::VisibleSelection(const Range* range, EAffinity affinity, bool VisibleSelection VisibleSelection::selectionFromContentsOfNode(Node* node) { - ASSERT(!editingIgnoresContent(node)); + ASSERT(!editingIgnoresContent(*node)); return VisibleSelection(firstPositionInNode(node), lastPositionInNode(node), DOWNSTREAM); } @@ -122,19 +123,21 @@ void VisibleSelection::setExtent(const VisiblePosition& visiblePosition) validate(); } -PassRefPtr<Range> VisibleSelection::firstRange() const +RefPtr<Range> VisibleSelection::firstRange() const { - if (isNone()) - return 0; + if (isNoneOrOrphaned()) + return nullptr; Position start = m_start.parentAnchoredEquivalent(); Position end = m_end.parentAnchoredEquivalent(); + if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan()) + return nullptr; return Range::create(start.anchorNode()->document(), start, end); } -PassRefPtr<Range> VisibleSelection::toNormalizedRange() const +RefPtr<Range> VisibleSelection::toNormalizedRange() const { - if (isNone()) - return 0; + if (isNoneOrOrphaned()) + return nullptr; // Make sure we have an updated layout since this function is called // in the course of running edit commands which modify the DOM. @@ -143,8 +146,8 @@ PassRefPtr<Range> VisibleSelection::toNormalizedRange() const m_start.anchorNode()->document().updateLayout(); // Check again, because updating layout can clear the selection. - if (isNone()) - return 0; + if (isNoneOrOrphaned()) + return nullptr; Position s, e; if (isCaret()) { @@ -180,7 +183,7 @@ PassRefPtr<Range> VisibleSelection::toNormalizedRange() const } if (!s.containerNode() || !e.containerNode()) - return 0; + return nullptr; // VisibleSelections are supposed to always be valid. This constructor will ASSERT // if a valid range could not be created, which is fine for this callsite. @@ -196,30 +199,26 @@ bool VisibleSelection::expandUsingGranularity(TextGranularity granularity) return true; } -static PassRefPtr<Range> makeSearchRange(const Position& pos) +static RefPtr<Range> makeSearchRange(const Position& position) { - Node* n = pos.deprecatedNode(); - if (!n) - return 0; - Node* de = n->document().documentElement(); - if (!de) - return 0; - Element* boundary = deprecatedEnclosingBlockFlowElement(n); + auto* node = position.deprecatedNode(); + if (!node) + return nullptr; + auto* boundary = deprecatedEnclosingBlockFlowElement(node); if (!boundary) - return 0; + return nullptr; - RefPtr<Range> searchRange(Range::create(n->document())); - ExceptionCode ec = 0; + auto searchRange = Range::create(node->document()); - Position start(pos.parentAnchoredEquivalent()); - searchRange->selectNodeContents(boundary, ec); - searchRange->setStart(start.containerNode(), start.offsetInContainerNode(), ec); + auto result = searchRange->selectNodeContents(*boundary); + if (result.hasException()) + return nullptr; + Position start { position.parentAnchoredEquivalent() }; + result = searchRange->setStart(*start.containerNode(), start.offsetInContainerNode()); + if (result.hasException()) + return nullptr; - ASSERT(!ec); - if (ec) - return 0; - - return searchRange.release(); + return WTFMove(searchRange); } bool VisibleSelection::isAll(EditingBoundaryCrossingRule rule) const @@ -233,10 +232,10 @@ void VisibleSelection::appendTrailingWhitespace() if (!searchRange) return; - CharacterIterator charIt(searchRange.get(), TextIteratorEmitsCharactersBetweenAllVisiblePositions); + CharacterIterator charIt(*searchRange, TextIteratorEmitsCharactersBetweenAllVisiblePositions); - for (; charIt.length(); charIt.advance(1)) { - UChar c = charIt.characters()[0]; + for (; !charIt.atEnd() && charIt.text().length(); charIt.advance(1)) { + UChar c = charIt.text()[0]; if ((!isSpaceOrNewline(c) && c != noBreakSpace) || c == '\n') break; m_end = charIt.range()->endPosition(); @@ -306,7 +305,7 @@ void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(Text // the next one) to match TextEdit. end = wordEnd.next(); - if (Node* table = isFirstPositionAfterTable(end)) { + if (auto* table = isFirstPositionAfterTable(end)) { // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table. if (isBlock(table)) @@ -321,14 +320,12 @@ void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(Text } m_end = end.deepEquivalent(); -#if PLATFORM(IOS) // End must not be before start. if (m_start.deprecatedNode() == m_end.deprecatedNode() && m_start.deprecatedEditingOffset() > m_end.deprecatedEditingOffset()) { Position swap(m_start); m_start = m_end; m_end = swap; } -#endif break; } case SentenceGranularity: { @@ -392,11 +389,9 @@ void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(Text m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent(); m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent(); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif } // Make sure we do not have a dangling start or end. @@ -478,13 +473,13 @@ static Position adjustPositionForEnd(const Position& currentPosition, Node* star ASSERT(¤tPosition.containerNode()->treeScope() != &treeScope); - if (Node* ancestor = treeScope.ancestorInThisScope(currentPosition.containerNode())) { + if (Node* ancestor = treeScope.ancestorNodeInThisScope(currentPosition.containerNode())) { if (ancestor->contains(startContainerNode)) return positionAfterNode(ancestor); return positionBeforeNode(ancestor); } - if (Node* lastChild = treeScope.rootNode()->lastChild()) + if (Node* lastChild = treeScope.rootNode().lastChild()) return positionAfterNode(lastChild); return Position(); @@ -496,13 +491,13 @@ static Position adjustPositionForStart(const Position& currentPosition, Node* en ASSERT(¤tPosition.containerNode()->treeScope() != &treeScope); - if (Node* ancestor = treeScope.ancestorInThisScope(currentPosition.containerNode())) { + if (Node* ancestor = treeScope.ancestorNodeInThisScope(currentPosition.containerNode())) { if (ancestor->contains(endContainerNode)) return positionBeforeNode(ancestor); return positionAfterNode(ancestor); } - if (Node* firstChild = treeScope.rootNode()->firstChild()) + if (Node* firstChild = treeScope.rootNode().firstChild()) return positionBeforeNode(firstChild); return Position(); @@ -532,18 +527,16 @@ void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() if (m_base.isNull() || m_start.isNull() || m_end.isNull()) return; -#if PLATFORM(IOS) - // Early return in the caret case (the state hasn't actually been set yet, so we can't use isCaret()) to avoid the + // Early return in the caret case (the state hasn't actually been set yet, so we can't use isCaret()) to avoid the // expense of computing highestEditableRoot. if (m_base == m_start && m_base == m_end) return; -#endif - Node* baseRoot = highestEditableRoot(m_base); - Node* startRoot = highestEditableRoot(m_start); - Node* endRoot = highestEditableRoot(m_end); + auto* baseRoot = highestEditableRoot(m_base); + auto* startRoot = highestEditableRoot(m_start); + auto* endRoot = highestEditableRoot(m_end); - Node* baseEditableAncestor = lowestEditableAncestor(m_base.containerNode()); + auto* baseEditableAncestor = lowestEditableAncestor(m_base.containerNode()); // The base, start and end are all in the same region. No adjustment necessary. if (baseRoot == startRoot && baseRoot == endRoot) @@ -578,7 +571,7 @@ void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() // The selection ends in editable content or non-editable content inside a different editable ancestor, // move backward until non-editable content inside the same lowest editable ancestor is reached. - Node* endEditableAncestor = lowestEditableAncestor(m_end.containerNode()); + auto* endEditableAncestor = lowestEditableAncestor(m_end.containerNode()); if (endRoot || endEditableAncestor != baseEditableAncestor) { Position p = previousVisuallyDistinctCandidate(m_end); @@ -608,7 +601,7 @@ void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() // The selection starts in editable content or non-editable content inside a different editable ancestor, // move forward until non-editable content inside the same lowest editable ancestor is reached. - Node* startEditableAncestor = lowestEditableAncestor(m_start.containerNode()); + auto* startEditableAncestor = lowestEditableAncestor(m_start.containerNode()); if (startRoot || startEditableAncestor != baseEditableAncestor) { Position p = nextVisuallyDistinctCandidate(m_start); Node* shadowAncestor = startRoot ? startRoot->shadowHost() : 0; @@ -648,7 +641,9 @@ bool VisibleSelection::isContentEditable() const bool VisibleSelection::hasEditableStyle() const { - return isEditablePosition(start(), ContentIsEditable, DoNotUpdateStyle); + if (Node* containerNode = start().containerNode()) + return containerNode->hasEditableStyle(); + return false; } bool VisibleSelection::isContentRichlyEditable() const @@ -663,10 +658,16 @@ Element* VisibleSelection::rootEditableElement() const Node* VisibleSelection::nonBoundaryShadowTreeRootNode() const { - return start().deprecatedNode() ? start().deprecatedNode()->nonBoundaryShadowTreeRootNode() : 0; + return start().deprecatedNode() ? start().deprecatedNode()->nonBoundaryShadowTreeRootNode() : nullptr; +} + +bool VisibleSelection::isInPasswordField() const +{ + HTMLTextFormControlElement* textControl = enclosingTextFormControl(start()); + return is<HTMLInputElement>(textControl) && downcast<HTMLInputElement>(*textControl).isPasswordField(); } -#ifndef NDEBUG +#if ENABLE(TREE_DEBUGGING) void VisibleSelection::debugPosition() const { @@ -723,7 +724,7 @@ void VisibleSelection::showTreeForThis() const } // namespace WebCore -#ifndef NDEBUG +#if ENABLE(TREE_DEBUGGING) void showTree(const WebCore::VisibleSelection& sel) { diff --git a/Source/WebCore/editing/VisibleSelection.h b/Source/WebCore/editing/VisibleSelection.h index 345f82deb..045f572d0 100644 --- a/Source/WebCore/editing/VisibleSelection.h +++ b/Source/WebCore/editing/VisibleSelection.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2004, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef VisibleSelection_h -#define VisibleSelection_h +#pragma once #include "TextGranularity.h" #include "VisiblePosition.h" @@ -40,17 +39,17 @@ class VisibleSelection { public: enum SelectionType { NoSelection, CaretSelection, RangeSelection }; - VisibleSelection(); + WEBCORE_EXPORT VisibleSelection(); VisibleSelection(const Position&, EAffinity, bool isDirectional = false); VisibleSelection(const Position&, const Position&, EAffinity = SEL_DEFAULT_AFFINITY, bool isDirectional = false); - VisibleSelection(const Range*, EAffinity = SEL_DEFAULT_AFFINITY, bool isDirectional = false); + WEBCORE_EXPORT VisibleSelection(const Range&, EAffinity = SEL_DEFAULT_AFFINITY, bool isDirectional = false); - VisibleSelection(const VisiblePosition&, bool isDirectional = false); - VisibleSelection(const VisiblePosition&, const VisiblePosition&, bool isDirectional = false); + WEBCORE_EXPORT VisibleSelection(const VisiblePosition&, bool isDirectional = false); + WEBCORE_EXPORT VisibleSelection(const VisiblePosition&, const VisiblePosition&, bool isDirectional = false); - static VisibleSelection selectionFromContentsOfNode(Node*); + WEBCORE_EXPORT static VisibleSelection selectionFromContentsOfNode(Node*); SelectionType selectionType() const { return m_selectionType; } @@ -77,35 +76,37 @@ public: bool isRange() const { return selectionType() == RangeSelection; } bool isCaretOrRange() const { return selectionType() != NoSelection; } bool isNonOrphanedRange() const { return isRange() && !start().isOrphan() && !end().isOrphan(); } - bool isNonOrphanedCaretOrRange() const { return isCaretOrRange() && !start().isOrphan() && !end().isOrphan(); } + bool isNoneOrOrphaned() const { return isNone() || start().isOrphan() || end().isOrphan(); } bool isBaseFirst() const { return m_baseIsFirst; } bool isDirectional() const { return m_isDirectional; } void setIsDirectional(bool isDirectional) { m_isDirectional = isDirectional; } - bool isAll(EditingBoundaryCrossingRule) const; + WEBCORE_EXPORT bool isAll(EditingBoundaryCrossingRule) const; void appendTrailingWhitespace(); - bool expandUsingGranularity(TextGranularity granularity); + WEBCORE_EXPORT bool expandUsingGranularity(TextGranularity granularity); // We don't yet support multi-range selections, so we only ever have one range to return. - PassRefPtr<Range> firstRange() const; + WEBCORE_EXPORT RefPtr<Range> firstRange() const; // FIXME: Most callers probably don't want this function, but are using it // for historical reasons. toNormalizedRange contracts the range around // text, and moves the caret upstream before returning the range. - PassRefPtr<Range> toNormalizedRange() const; + WEBCORE_EXPORT RefPtr<Range> toNormalizedRange() const; - Element* rootEditableElement() const; - bool isContentEditable() const; + WEBCORE_EXPORT Element* rootEditableElement() const; + WEBCORE_EXPORT bool isContentEditable() const; bool hasEditableStyle() const; - bool isContentRichlyEditable() const; + WEBCORE_EXPORT bool isContentRichlyEditable() const; // Returns a shadow tree node for legacy shadow trees, a child of the // ShadowRoot node for new shadow trees, or 0 for non-shadow trees. Node* nonBoundaryShadowTreeRootNode() const; -#ifndef NDEBUG + WEBCORE_EXPORT bool isInPasswordField() const; + +#if ENABLE(TREE_DEBUGGING) void debugPosition() const; void formatForDebugger(char* buffer, unsigned length) const; void showTreeForThis() const; @@ -154,10 +155,8 @@ inline bool operator!=(const VisibleSelection& a, const VisibleSelection& b) } // namespace WebCore -#ifndef NDEBUG -// Outside the WebCore namespace for ease of invocation from gdb. +#if ENABLE(TREE_DEBUGGING) +// Outside the WebCore namespace for ease of invocation from the debugger. void showTree(const WebCore::VisibleSelection&); void showTree(const WebCore::VisibleSelection*); #endif - -#endif // VisibleSelection_h diff --git a/Source/WebCore/editing/VisibleUnits.cpp b/Source/WebCore/editing/VisibleUnits.cpp index b082b3dac..4363c1be7 100644 --- a/Source/WebCore/editing/VisibleUnits.cpp +++ b/Source/WebCore/editing/VisibleUnits.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -27,7 +27,8 @@ #include "VisibleUnits.h" #include "Document.h" -#include "Element.h" +#include "HTMLBRElement.h" +#include "HTMLElement.h" #include "HTMLNames.h" #include "InlineTextBox.h" #include "NodeTraversal.h" @@ -36,9 +37,10 @@ #include "RenderedPosition.h" #include "Text.h" #include "TextBoundaries.h" -#include "TextBreakIterator.h" #include "TextIterator.h" #include "VisibleSelection.h" +#include <unicode/ubrk.h> +#include <wtf/text/TextBreakIterator.h> #include "htmlediting.h" namespace WebCore { @@ -48,35 +50,35 @@ using namespace WTF::Unicode; static Node* previousLeafWithSameEditability(Node* node, EditableType editableType) { - bool editable = node->hasEditableStyle(editableType); + bool editable = hasEditableStyle(*node, editableType); node = previousLeafNode(node); while (node) { - if (editable == node->hasEditableStyle(editableType)) + if (editable == hasEditableStyle(*node, editableType)) return node; node = previousLeafNode(node); } - return 0; + return nullptr; } -static Node* nextLeafWithSameEditability(Node* node, EditableType editableType = ContentIsEditable) +static Node* nextLeafWithSameEditability(Node* node, EditableType editableType) { if (!node) - return 0; + return nullptr; - bool editable = node->hasEditableStyle(editableType); + bool editable = hasEditableStyle(*node, editableType); node = nextLeafNode(node); while (node) { - if (editable == node->hasEditableStyle(editableType)) + if (editable == hasEditableStyle(*node, editableType)) return node; node = nextLeafNode(node); } - return 0; + return nullptr; } // FIXME: consolidate with code in previousLinePosition. static Position previousRootInlineBoxCandidatePosition(Node* node, const VisiblePosition& visiblePosition, EditableType editableType) { - Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), editableType); + auto* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), editableType); Node* previousNode = previousLeafWithSameEditability(node, editableType); while (previousNode && (!previousNode->renderer() || inSameLine(firstPositionInOrBeforeNode(previousNode), visiblePosition))) @@ -87,7 +89,7 @@ static Position previousRootInlineBoxCandidatePosition(Node* node, const Visible break; Position pos = previousNode->hasTagName(brTag) ? positionBeforeNode(previousNode) : - createLegacyEditingPosition(previousNode, caretMaxOffset(previousNode)); + createLegacyEditingPosition(previousNode, caretMaxOffset(*previousNode)); if (pos.isCandidate()) return pos; @@ -99,7 +101,7 @@ static Position previousRootInlineBoxCandidatePosition(Node* node, const Visible static Position nextRootInlineBoxCandidatePosition(Node* node, const VisiblePosition& visiblePosition, EditableType editableType) { - Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), editableType); + auto* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), editableType); Node* nextNode = nextLeafWithSameEditability(node, editableType); while (nextNode && (!nextNode->renderer() || inSameLine(firstPositionInOrBeforeNode(nextNode), visiblePosition))) nextNode = nextLeafWithSameEditability(nextNode, ContentIsEditable); @@ -109,7 +111,7 @@ static Position nextRootInlineBoxCandidatePosition(Node* node, const VisiblePosi break; Position pos; - pos = createLegacyEditingPosition(nextNode, caretMinOffset(nextNode)); + pos = createLegacyEditingPosition(nextNode, caretMinOffset(*nextNode)); if (pos.isCandidate()) return pos; @@ -133,16 +135,18 @@ private: const Vector<InlineBox*>& collectBoxes(const RootInlineBox*); int boxIndexInLeaves(const InlineTextBox*) const; - const RootInlineBox* m_rootInlineBox; + const RootInlineBox* m_rootInlineBox { nullptr }; Vector<InlineBox*> m_leafBoxes; }; -CachedLogicallyOrderedLeafBoxes::CachedLogicallyOrderedLeafBoxes() : m_rootInlineBox(0) { }; +CachedLogicallyOrderedLeafBoxes::CachedLogicallyOrderedLeafBoxes() +{ +} const InlineBox* CachedLogicallyOrderedLeafBoxes::previousTextOrLineBreakBox(const RootInlineBox* root, const InlineTextBox* box) { if (!root) - return 0; + return nullptr; collectBoxes(root); @@ -157,13 +161,13 @@ const InlineBox* CachedLogicallyOrderedLeafBoxes::previousTextOrLineBreakBox(con return box; } - return 0; + return nullptr; } const InlineBox* CachedLogicallyOrderedLeafBoxes::nextTextOrLineBreakBox(const RootInlineBox* root, const InlineTextBox* box) { if (!root) - return 0; + return nullptr; collectBoxes(root); @@ -179,7 +183,7 @@ const InlineBox* CachedLogicallyOrderedLeafBoxes::nextTextOrLineBreakBox(const R return box; } - return 0; + return nullptr; } const Vector<InlineBox*>& CachedLogicallyOrderedLeafBoxes::collectBoxes(const RootInlineBox* root) @@ -282,7 +286,7 @@ static const InlineBox* logicallyNextBox(const VisiblePosition& visiblePosition, return 0; } -static TextBreakIterator* wordBreakIteratorForMinOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, +static UBreakIterator* wordBreakIteratorForMinOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, int& previousBoxLength, bool& previousBoxInDifferentBlock, Vector<UChar, 1024>& string, CachedLogicallyOrderedLeafBoxes& leafBoxes) { previousBoxInDifferentBlock = false; @@ -290,21 +294,19 @@ static TextBreakIterator* wordBreakIteratorForMinOffsetBoundary(const VisiblePos // FIXME: Handle the case when we don't have an inline text box. const InlineBox* previousBox = logicallyPreviousBox(visiblePosition, textBox, previousBoxInDifferentBlock, leafBoxes); - int len = 0; string.clear(); - if (previousBox && previousBox->isInlineTextBox()) { - const InlineTextBox* previousTextBox = toInlineTextBox(previousBox); - previousBoxLength = previousTextBox->len(); - string.append(previousTextBox->renderer().text()->deprecatedCharacters() + previousTextBox->start(), previousBoxLength); - len += previousBoxLength; + + if (is<InlineTextBox>(previousBox)) { + const auto& previousTextBox = downcast<InlineTextBox>(*previousBox); + previousBoxLength = previousTextBox.len(); + append(string, StringView(previousTextBox.renderer().text()).substring(previousTextBox.start(), previousBoxLength)); } - string.append(textBox->renderer().text()->deprecatedCharacters() + textBox->start(), textBox->len()); - len += textBox->len(); + append(string, StringView(textBox->renderer().text()).substring(textBox->start(), textBox->len())); - return wordBreakIterator(StringView(string.data(), len)); + return wordBreakIterator(StringView(string.data(), string.size())); } -static TextBreakIterator* wordBreakIteratorForMaxOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, +static UBreakIterator* wordBreakIteratorForMaxOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, bool& nextBoxInDifferentBlock, Vector<UChar, 1024>& string, CachedLogicallyOrderedLeafBoxes& leafBoxes) { nextBoxInDifferentBlock = false; @@ -312,33 +314,30 @@ static TextBreakIterator* wordBreakIteratorForMaxOffsetBoundary(const VisiblePos // FIXME: Handle the case when we don't have an inline text box. const InlineBox* nextBox = logicallyNextBox(visiblePosition, textBox, nextBoxInDifferentBlock, leafBoxes); - int len = 0; string.clear(); - string.append(textBox->renderer().text()->deprecatedCharacters() + textBox->start(), textBox->len()); - len += textBox->len(); - if (nextBox && nextBox->isInlineTextBox()) { - const InlineTextBox* nextTextBox = toInlineTextBox(nextBox); - string.append(nextTextBox->renderer().text()->deprecatedCharacters() + nextTextBox->start(), nextTextBox->len()); - len += nextTextBox->len(); + append(string, StringView(textBox->renderer().text()).substring(textBox->start(), textBox->len())); + if (is<InlineTextBox>(nextBox)) { + const auto& nextTextBox = downcast<InlineTextBox>(*nextBox); + append(string, StringView(nextTextBox.renderer().text()).substring(nextTextBox.start(), nextTextBox.len())); } - return wordBreakIterator(StringView(string.data(), len)); + return wordBreakIterator(StringView(string.data(), string.size())); } -static bool isLogicalStartOfWord(TextBreakIterator* iter, int position, bool hardLineBreak) +static bool isLogicalStartOfWord(UBreakIterator* iter, int position, bool hardLineBreak) { - bool boundary = hardLineBreak ? true : isTextBreak(iter, position); + bool boundary = hardLineBreak ? true : ubrk_isBoundary(iter, position); if (!boundary) return false; - textBreakFollowing(iter, position); + ubrk_following(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) +static bool islogicalEndOfWord(UBreakIterator* iter, int position, bool hardLineBreak) { - bool boundary = isTextBreak(iter, position); + bool boundary = ubrk_isBoundary(iter, position); return (hardLineBreak || boundary) && isWordTextBreak(iter); } @@ -351,9 +350,10 @@ static VisiblePosition visualWordPosition(const VisiblePosition& visiblePosition return VisiblePosition(); TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); - InlineBox* previouslyVisitedBox = 0; + InlineBox* previouslyVisitedBox = nullptr; VisiblePosition current = visiblePosition; - TextBreakIterator* iter = 0; + std::optional<VisiblePosition> previousPosition; + UBreakIterator* iter = nullptr; CachedLogicallyOrderedLeafBoxes leafBoxes; Vector<UChar, 1024> string; @@ -362,6 +362,9 @@ static VisiblePosition visualWordPosition(const VisiblePosition& visiblePosition VisiblePosition adjacentCharacterPosition = direction == MoveRight ? current.right(true) : current.left(true); if (adjacentCharacterPosition == current || adjacentCharacterPosition.isNull()) return VisiblePosition(); + // FIXME: This is a workaround for webkit.org/b/167138. + if (previousPosition && adjacentCharacterPosition == previousPosition.value()) + return VisiblePosition(); InlineBox* box; int offsetInBox; @@ -369,47 +372,48 @@ static VisiblePosition visualWordPosition(const VisiblePosition& visiblePosition if (!box) break; - if (!box->isInlineTextBox()) { + if (!is<InlineTextBox>(*box)) { current = adjacentCharacterPosition; continue; } - InlineTextBox* textBox = toInlineTextBox(box); + InlineTextBox& textBox = downcast<InlineTextBox>(*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); + iter = wordBreakIteratorForMinOffsetBoundary(visiblePosition, &textBox, previousBoxLength, previousBoxInDifferentBlock, string, leafBoxes); else if (offsetInBox == box->caretMaxOffset()) - iter = wordBreakIteratorForMaxOffsetBoundary(visiblePosition, textBox, nextBoxInDifferentBlock, string, leafBoxes); + iter = wordBreakIteratorForMaxOffsetBoundary(visiblePosition, &textBox, nextBoxInDifferentBlock, string, leafBoxes); else if (movingIntoNewBox) { - iter = wordBreakIterator(StringView(textBox->renderer().text()).substring(textBox->start(), textBox->len())); + iter = wordBreakIterator(StringView(textBox.renderer().text()).substring(textBox.start(), textBox.len())); previouslyVisitedBox = box; } if (!iter) break; - textBreakFirst(iter); - int offsetInIterator = offsetInBox - textBox->start() + previousBoxLength; + ubrk_first(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; + 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; + bool logicalEndInRenderer = offsetInBox == static_cast<int>(textBox.start() + textBox.len()) && nextBoxInDifferentBlock; isWordBreak = islogicalEndOfWord(iter, offsetInIterator, logicalEndInRenderer); } if (isWordBreak) return adjacentCharacterPosition; + previousPosition = current; current = adjacentCharacterPosition; } return VisiblePosition(); @@ -442,90 +446,177 @@ VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition, bool s } -enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext }; +static void prepend(Vector<UChar, 1024>& buffer, StringView string) +{ + unsigned oldSize = buffer.size(); + unsigned length = string.length(); + buffer.grow(oldSize + length); + memmove(buffer.data() + length, buffer.data(), oldSize * sizeof(UChar)); + for (unsigned i = 0; i < length; ++i) + buffer[i] = string[i]; +} -typedef unsigned (*BoundarySearchFunction)(const UChar*, unsigned length, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext); +static void prependRepeatedCharacter(Vector<UChar, 1024>& buffer, UChar character, unsigned count) +{ + unsigned oldSize = buffer.size(); + buffer.grow(oldSize + count); + memmove(buffer.data() + count, buffer.data(), oldSize * sizeof(UChar)); + for (unsigned i = 0; i < count; ++i) + buffer[i] = character; +} -static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) +static void appendRepeatedCharacter(Vector<UChar, 1024>& buffer, UChar character, unsigned count) { - Position pos = c.deepEquivalent(); - Node* boundary = pos.parentEditingBoundary(); - if (!boundary) - return VisiblePosition(); + unsigned oldSize = buffer.size(); + buffer.grow(oldSize + count); + for (unsigned i = 0; i < count; ++i) + buffer[oldSize + i] = character; +} - Document& boundaryDocument = boundary->document(); - Position start = createLegacyEditingPosition(boundary, 0).parentAnchoredEquivalent(); - Position end = pos.parentAnchoredEquivalent(); - RefPtr<Range> searchRange = Range::create(boundaryDocument); - - Vector<UChar, 1024> string; +unsigned suffixLengthForRange(const Range& forwardsScanRange, Vector<UChar, 1024>& string) +{ unsigned suffixLength = 0; - - ExceptionCode ec = 0; - if (requiresContextForWordBoundary(c.characterBefore())) { - RefPtr<Range> forwardsScanRange(boundaryDocument.createRange()); - forwardsScanRange->setEndAfter(boundary, ec); - forwardsScanRange->setStart(end.deprecatedNode(), end.deprecatedEditingOffset(), ec); - TextIterator forwardsIterator(forwardsScanRange.get()); - while (!forwardsIterator.atEnd()) { - const UChar* characters = forwardsIterator.characters(); - int length = forwardsIterator.length(); - int i = endOfFirstWordBoundaryContext(characters, length); - string.append(characters, i); - suffixLength += i; - if (i < length) - break; - forwardsIterator.advance(); - } + TextIterator forwardsIterator(&forwardsScanRange); + while (!forwardsIterator.atEnd()) { + StringView text = forwardsIterator.text(); + unsigned i = endOfFirstWordBoundaryContext(text); + append(string, text.substring(0, i)); + suffixLength += i; + if (i < text.length()) + break; + forwardsIterator.advance(); } + return suffixLength; +} - searchRange->setStart(start.deprecatedNode(), start.deprecatedEditingOffset(), ec); - searchRange->setEnd(end.deprecatedNode(), end.deprecatedEditingOffset(), ec); - - ASSERT(!ec); - if (ec) - return VisiblePosition(); +unsigned prefixLengthForRange(const Range& backwardsScanRange, Vector<UChar, 1024>& string) +{ + unsigned prefixLength = 0; + SimplifiedBackwardsTextIterator backwardsIterator(backwardsScanRange); + while (!backwardsIterator.atEnd()) { + StringView text = backwardsIterator.text(); + int i = startOfLastWordBoundaryContext(text); + prepend(string, text.substring(i)); + prefixLength += text.length() - i; + if (i > 0) + break; + backwardsIterator.advance(); + } + return prefixLength; +} - SimplifiedBackwardsTextIterator it(searchRange.get()); +unsigned backwardSearchForBoundaryWithTextIterator(SimplifiedBackwardsTextIterator& it, Vector<UChar, 1024>& string, unsigned suffixLength, BoundarySearchFunction searchFunction) +{ unsigned next = 0; bool needMoreContext = false; while (!it.atEnd()) { bool inTextSecurityMode = it.node() && it.node()->renderer() && it.node()->renderer()->style().textSecurity() != TSNONE; // iterate to get chunks until the searchFunction returns a non-zero value. - if (!inTextSecurityMode) - string.insert(0, it.characters(), it.length()); + if (!inTextSecurityMode) + prepend(string, it.text()); else { // Treat bullets used in the text security mode as regular characters when looking for boundaries - String iteratorString(it.characters(), it.length()); -#if PLATFORM(IOS) - iteratorString = iteratorString.impl()->fill('x'); -#else - iteratorString.fill('x'); -#endif - string.insert(0, iteratorString.deprecatedCharacters(), iteratorString.length()); + prependRepeatedCharacter(string, 'x', it.text().length()); + } + if (string.size() > suffixLength) { + next = searchFunction(StringView(string.data(), string.size()), string.size() - suffixLength, MayHaveMoreContext, needMoreContext); + if (next > 1) // FIXME: This is a work around for https://webkit.org/b/115070. We need to provide more contexts in general case. + break; } - next = searchFunction(string.data(), string.size(), string.size() - suffixLength, MayHaveMoreContext, needMoreContext); - if (next > 1) // FIXME: This is a work around for https://webkit.org/b/115070. We need to provide more contexts in general case. - break; it.advance(); } - if (needMoreContext) { + if (needMoreContext && string.size() > suffixLength) { // The last search returned the beginning of the buffer and asked for more context, // but there is no earlier text. Force a search with what's available. - next = searchFunction(string.data(), string.size(), string.size() - suffixLength, DontHaveMoreContext, needMoreContext); + next = searchFunction(StringView(string.data(), string.size()), string.size() - suffixLength, DontHaveMoreContext, needMoreContext); ASSERT(!needMoreContext); } + + return next; +} + +unsigned forwardSearchForBoundaryWithTextIterator(TextIterator& it, Vector<UChar, 1024>& string, unsigned prefixLength, BoundarySearchFunction searchFunction) +{ + unsigned next = 0; + bool needMoreContext = false; + while (!it.atEnd()) { + bool inTextSecurityMode = it.node() && it.node()->renderer() && it.node()->renderer()->style().textSecurity() != TSNONE; + // Keep asking the iterator for chunks until the search function + // returns an end value not equal to the length of the string passed to it. + if (!inTextSecurityMode) + append(string, it.text()); + else { + // Treat bullets used in the text security mode as regular characters when looking for boundaries + appendRepeatedCharacter(string, 'x', it.text().length()); + } + if (string.size() > prefixLength) { + next = searchFunction(StringView(string.data(), string.size()), prefixLength, MayHaveMoreContext, needMoreContext); + if (next != string.size()) + break; + } + it.advance(); + } + if (needMoreContext && string.size() > prefixLength) { + // The last search returned the end of the buffer and asked for more context, + // but there is no further text. Force a search with what's available. + next = searchFunction(StringView(string.data(), string.size()), prefixLength, DontHaveMoreContext, needMoreContext); + ASSERT(!needMoreContext); + } + + return next; +} + +static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) +{ + Position pos = c.deepEquivalent(); + Node* boundary = pos.parentEditingBoundary(); + if (!boundary) + return VisiblePosition(); + + Document& boundaryDocument = boundary->document(); + Position start = createLegacyEditingPosition(boundary, 0).parentAnchoredEquivalent(); + Position end = pos.parentAnchoredEquivalent(); + + if (start.isNull() || end.isNull()) + return VisiblePosition(); + + Ref<Range> searchRange = Range::create(boundaryDocument); + + Vector<UChar, 1024> string; + unsigned suffixLength = 0; + + if (requiresContextForWordBoundary(c.characterBefore())) { + auto forwardsScanRange = boundaryDocument.createRange(); + auto result = forwardsScanRange->setEndAfter(*boundary); + if (result.hasException()) + return { }; + result = forwardsScanRange->setStart(*end.deprecatedNode(), end.deprecatedEditingOffset()); + if (result.hasException()) + return { }; + suffixLength = suffixLengthForRange(forwardsScanRange, string); + } + + auto result = searchRange->setStart(*start.deprecatedNode(), start.deprecatedEditingOffset()); + if (result.hasException()) + return { }; + result = searchRange->setEnd(*end.deprecatedNode(), end.deprecatedEditingOffset()); + if (result.hasException()) + return { }; + + SimplifiedBackwardsTextIterator it(searchRange); + unsigned next = backwardSearchForBoundaryWithTextIterator(it, string, suffixLength, searchFunction); if (!next) - return VisiblePosition(it.atEnd() ? it.range()->startPosition() : pos, DOWNSTREAM); + return VisiblePosition(it.atEnd() ? searchRange->startPosition() : pos, DOWNSTREAM); - Node* node = it.range()->startContainer(); - if ((node->isTextNode() && static_cast<int>(next) <= node->maxCharacterOffset()) || (node->renderer() && node->renderer()->isBR() && !next)) + Node& node = it.atEnd() ? searchRange->startContainer() : it.range()->startContainer(); + if ((node.isTextNode() && static_cast<int>(next) <= node.maxCharacterOffset()) || (node.renderer() && node.renderer()->isBR() && !next)) { // The next variable contains a usable index into a text node - return VisiblePosition(createLegacyEditingPosition(node, next), DOWNSTREAM); + return VisiblePosition(createLegacyEditingPosition(&node, next), DOWNSTREAM); + } // Use the character iterator to translate the next value into a DOM position. - BackwardsCharacterIterator charIt(searchRange.get()); + BackwardsCharacterIterator charIt(searchRange); charIt.advance(string.size() - suffixLength - next); // FIXME: charIt can get out of shadow host. return VisiblePosition(charIt.range()->endPosition(), DOWNSTREAM); @@ -539,71 +630,35 @@ static VisiblePosition nextBoundary(const VisiblePosition& c, BoundarySearchFunc return VisiblePosition(); Document& boundaryDocument = boundary->document(); - RefPtr<Range> searchRange(boundaryDocument.createRange()); + Ref<Range> searchRange = boundaryDocument.createRange(); Position start(pos.parentAnchoredEquivalent()); Vector<UChar, 1024> string; unsigned prefixLength = 0; if (requiresContextForWordBoundary(c.characterAfter())) { - RefPtr<Range> backwardsScanRange(boundaryDocument.createRange()); - backwardsScanRange->setEnd(start.deprecatedNode(), start.deprecatedEditingOffset(), IGNORE_EXCEPTION); - SimplifiedBackwardsTextIterator backwardsIterator(backwardsScanRange.get()); - while (!backwardsIterator.atEnd()) { - const UChar* characters = backwardsIterator.characters(); - int length = backwardsIterator.length(); - int i = startOfLastWordBoundaryContext(characters, length); - string.insert(0, characters + i, length - i); - prefixLength += length - i; - if (i > 0) - break; - backwardsIterator.advance(); - } + auto backwardsScanRange = boundaryDocument.createRange(); + if (start.deprecatedNode()) + backwardsScanRange->setEnd(*start.deprecatedNode(), start.deprecatedEditingOffset()); + prefixLength = prefixLengthForRange(backwardsScanRange, string); } - searchRange->selectNodeContents(boundary, IGNORE_EXCEPTION); - searchRange->setStart(start.deprecatedNode(), start.deprecatedEditingOffset(), IGNORE_EXCEPTION); - TextIterator it(searchRange.get(), TextIteratorEmitsCharactersBetweenAllVisiblePositions); - unsigned next = 0; - bool needMoreContext = false; - while (!it.atEnd()) { - bool inTextSecurityMode = it.node() && it.node()->renderer() && it.node()->renderer()->style().textSecurity() != TSNONE; - // Keep asking the iterator for chunks until the search function - // returns an end value not equal to the length of the string passed to it. - if (!inTextSecurityMode) - string.append(it.characters(), it.length()); - else { - // Treat bullets used in the text security mode as regular characters when looking for boundaries - String iteratorString(it.characters(), it.length()); -#if PLATFORM(IOS) - iteratorString = iteratorString.impl()->fill('x'); -#else - iteratorString.fill('x'); -#endif - string.append(iteratorString.deprecatedCharacters(), iteratorString.length()); - } - next = searchFunction(string.data(), string.size(), prefixLength, MayHaveMoreContext, needMoreContext); - if (next != string.size()) - break; - it.advance(); - } - if (needMoreContext) { - // The last search returned the end of the buffer and asked for more context, - // but there is no further text. Force a search with what's available. - next = searchFunction(string.data(), string.size(), prefixLength, DontHaveMoreContext, needMoreContext); - ASSERT(!needMoreContext); - } + searchRange->selectNodeContents(*boundary); + if (start.deprecatedNode()) + searchRange->setStart(*start.deprecatedNode(), start.deprecatedEditingOffset()); + TextIterator it(searchRange.ptr(), TextIteratorEmitsCharactersBetweenAllVisiblePositions); + unsigned next = forwardSearchForBoundaryWithTextIterator(it, string, prefixLength, searchFunction); - if (it.atEnd() && next == string.size()) { - pos = it.range()->startPosition(); - } else if (next != prefixLength) { + if (it.atEnd() && next == string.size()) + pos = searchRange->endPosition(); + else if (next > prefixLength) { // Use the character iterator to translate the next value into a DOM position. - CharacterIterator charIt(searchRange.get(), TextIteratorEmitsCharactersBetweenAllVisiblePositions); + CharacterIterator charIt(searchRange, TextIteratorEmitsCharactersBetweenAllVisiblePositions); charIt.advance(next - prefixLength - 1); RefPtr<Range> characterRange = charIt.range(); pos = characterRange->endPosition(); - if (*charIt.characters() == '\n') { + if (charIt.text()[0] == '\n') { // FIXME: workaround for collapsed range (where only start position is correct) emitted for some emitted newlines (see rdar://5192593) VisiblePosition visPos = VisiblePosition(pos); if (visPos == VisiblePosition(characterRange->startPosition())) { @@ -619,21 +674,21 @@ static VisiblePosition nextBoundary(const VisiblePosition& c, BoundarySearchFunc // --------- -static unsigned startWordBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) +unsigned startWordBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) { ASSERT(offset); - if (mayHaveMoreContext && !startOfLastWordBoundaryContext(characters, offset)) { + if (mayHaveMoreContext && !startOfLastWordBoundaryContext(text.substring(0, offset))) { needMoreContext = true; return 0; } needMoreContext = false; int start, end; - U16_BACK_1(characters, 0, offset); - findWordBoundary(characters, length, offset, &start, &end); + U16_BACK_1(text, 0, offset); + findWordBoundary(text, offset, &start, &end); return start; } -VisiblePosition startOfWord(const VisiblePosition &c, EWordSide side) +VisiblePosition startOfWord(const VisiblePosition& c, EWordSide side) { // FIXME: This returns a null VP for c at the start of the document // and side == LeftWordIfOnBoundary @@ -650,26 +705,20 @@ VisiblePosition startOfWord(const VisiblePosition &c, EWordSide side) return previousBoundary(p, startWordBoundary); } -static unsigned endWordBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) +unsigned endWordBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) { - ASSERT(offset <= length); - if (mayHaveMoreContext && endOfFirstWordBoundaryContext(characters + offset, length - offset) == static_cast<int>(length - offset)) { + ASSERT(offset <= text.length()); + if (mayHaveMoreContext && endOfFirstWordBoundaryContext(text.substring(offset)) == text.length() - offset) { needMoreContext = true; - return length; + return text.length(); } needMoreContext = false; -#if PLATFORM(IOS) - // FIXME: Bug 126830: [iOS] Implement WebCore::findEndWordBoundary() - int start, end; - findWordBoundary(characters, length, offset, &start, &end); -#else int end; - findEndWordBoundary(characters, length, offset, &end); -#endif + findEndWordBoundary(text, offset, &end); return end; } -VisiblePosition endOfWord(const VisiblePosition &c, EWordSide side) +VisiblePosition endOfWord(const VisiblePosition& c, EWordSide side) { VisiblePosition p = c; if (side == LeftWordIfOnBoundary) { @@ -685,36 +734,34 @@ VisiblePosition endOfWord(const VisiblePosition &c, EWordSide side) return nextBoundary(p, endWordBoundary); } -static unsigned previousWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) +static unsigned previousWordPositionBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) { - if (mayHaveMoreContext && !startOfLastWordBoundaryContext(characters, offset)) { + if (mayHaveMoreContext && !startOfLastWordBoundaryContext(text.substring(0, offset))) { needMoreContext = true; return 0; } needMoreContext = false; - return findNextWordFromIndex(characters, length, offset, false); + return findNextWordFromIndex(text, offset, false); } -VisiblePosition previousWordPosition(const VisiblePosition &c) +VisiblePosition previousWordPosition(const VisiblePosition& position) { - VisiblePosition prev = previousBoundary(c, previousWordPositionBoundary); - return c.honorEditingBoundaryAtOrBefore(prev); + return position.honorEditingBoundaryAtOrBefore(previousBoundary(position, previousWordPositionBoundary)); } -static unsigned nextWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) +static unsigned nextWordPositionBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) { - if (mayHaveMoreContext && endOfFirstWordBoundaryContext(characters + offset, length - offset) == static_cast<int>(length - offset)) { + if (mayHaveMoreContext && endOfFirstWordBoundaryContext(text.substring(offset)) == text.length() - offset) { needMoreContext = true; - return length; + return text.length(); } needMoreContext = false; - return findNextWordFromIndex(characters, length, offset, true); + return findNextWordFromIndex(text, offset, true); } -VisiblePosition nextWordPosition(const VisiblePosition &c) +VisiblePosition nextWordPosition(const VisiblePosition& position) { - VisiblePosition next = nextBoundary(c, nextWordPositionBoundary); - return c.honorEditingBoundaryAtOrAfter(next); + return position.honorEditingBoundaryAtOrAfter(nextBoundary(position, nextWordPositionBoundary)); } bool isStartOfWord(const VisiblePosition& p) @@ -763,35 +810,41 @@ static VisiblePosition startPositionForLine(const VisiblePosition& c, LineEndpoi } } - return startNode->isTextNode() ? Position(toText(startNode), toInlineTextBox(startBox)->start()) + return is<Text>(*startNode) ? Position(downcast<Text>(startNode), downcast<InlineTextBox>(*startBox).start()) : positionBeforeNode(startNode); } -static VisiblePosition startOfLine(const VisiblePosition& c, LineEndpointComputationMode mode) +static VisiblePosition startOfLine(const VisiblePosition& c, LineEndpointComputationMode mode, bool* reachedBoundary) { + if (reachedBoundary) + *reachedBoundary = false; // TODO: this is the current behavior that might need to be fixed. // Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail. VisiblePosition visPos = startPositionForLine(c, mode); if (mode == UseLogicalOrdering) { if (Node* editableRoot = highestEditableRoot(c.deepEquivalent())) { - if (!editableRoot->contains(visPos.deepEquivalent().containerNode())) - return firstPositionInNode(editableRoot); + if (!editableRoot->contains(visPos.deepEquivalent().containerNode())) { + VisiblePosition newPosition = firstPositionInNode(editableRoot); + if (reachedBoundary) + *reachedBoundary = c == newPosition; + return newPosition; + } } } - return c.honorEditingBoundaryAtOrBefore(visPos); + return c.honorEditingBoundaryAtOrBefore(visPos, reachedBoundary); } // FIXME: Rename this function to reflect the fact it ignores bidi levels. VisiblePosition startOfLine(const VisiblePosition& currentPosition) { - return startOfLine(currentPosition, UseInlineBoxOrdering); + return startOfLine(currentPosition, UseInlineBoxOrdering, nullptr); } -VisiblePosition logicalStartOfLine(const VisiblePosition& currentPosition) +VisiblePosition logicalStartOfLine(const VisiblePosition& currentPosition, bool* reachedBoundary) { - return startOfLine(currentPosition, UseLogicalOrdering); + return startOfLine(currentPosition, UseLogicalOrdering, reachedBoundary); } static VisiblePosition endPositionForLine(const VisiblePosition& c, LineEndpointComputationMode mode) @@ -832,14 +885,14 @@ static VisiblePosition endPositionForLine(const VisiblePosition& c, LineEndpoint } Position pos; - if (endNode->hasTagName(brTag)) + if (is<HTMLBRElement>(*endNode)) pos = positionBeforeNode(endNode); - else if (endBox->isInlineTextBox() && endNode->isTextNode()) { - InlineTextBox* endTextBox = toInlineTextBox(endBox); - int endOffset = endTextBox->start(); - if (!endTextBox->isLineBreak()) - endOffset += endTextBox->len(); - pos = Position(toText(endNode), endOffset); + else if (is<InlineTextBox>(*endBox) && is<Text>(*endNode)) { + auto& endTextBox = downcast<InlineTextBox>(*endBox); + int endOffset = endTextBox.start(); + if (!endTextBox.isLineBreak()) + endOffset += endTextBox.len(); + pos = Position(downcast<Text>(endNode), endOffset); } else pos = positionAfterNode(endNode); @@ -851,8 +904,10 @@ static bool inSameLogicalLine(const VisiblePosition& a, const VisiblePosition& b return a.isNotNull() && logicalStartOfLine(a) == logicalStartOfLine(b); } -static VisiblePosition endOfLine(const VisiblePosition& c, LineEndpointComputationMode mode) +static VisiblePosition endOfLine(const VisiblePosition& c, LineEndpointComputationMode mode, bool* reachedBoundary) { + if (reachedBoundary) + *reachedBoundary = false; // TODO: this is the current behavior that might need to be fixed. // Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail. VisiblePosition visPos = endPositionForLine(c, mode); @@ -867,11 +922,15 @@ static VisiblePosition endOfLine(const VisiblePosition& c, LineEndpointComputati visPos = visPos.previous(); if (Node* editableRoot = highestEditableRoot(c.deepEquivalent())) { - if (!editableRoot->contains(visPos.deepEquivalent().containerNode())) - return lastPositionInNode(editableRoot); + if (!editableRoot->contains(visPos.deepEquivalent().containerNode())) { + VisiblePosition newPosition = lastPositionInNode(editableRoot); + if (reachedBoundary) + *reachedBoundary = c == newPosition; + return newPosition; + } } - return c.honorEditingBoundaryAtOrAfter(visPos); + return c.honorEditingBoundaryAtOrAfter(visPos, reachedBoundary); } // Make sure the end of line is at the same line as the given input position. Else use the previous position to @@ -886,50 +945,59 @@ static VisiblePosition endOfLine(const VisiblePosition& c, LineEndpointComputati visPos = endPositionForLine(visPos, UseInlineBoxOrdering); } - return c.honorEditingBoundaryAtOrAfter(visPos); + return c.honorEditingBoundaryAtOrAfter(visPos, reachedBoundary); } // FIXME: Rename this function to reflect the fact it ignores bidi levels. VisiblePosition endOfLine(const VisiblePosition& currentPosition) { - return endOfLine(currentPosition, UseInlineBoxOrdering); + return endOfLine(currentPosition, UseInlineBoxOrdering, nullptr); } -VisiblePosition logicalEndOfLine(const VisiblePosition& currentPosition) +VisiblePosition logicalEndOfLine(const VisiblePosition& currentPosition, bool* reachedBoundary) { - return endOfLine(currentPosition, UseLogicalOrdering); + return endOfLine(currentPosition, UseLogicalOrdering, reachedBoundary); } -bool inSameLine(const VisiblePosition &a, const VisiblePosition &b) +bool inSameLine(const VisiblePosition& a, const VisiblePosition& b) { return a.isNotNull() && startOfLine(a) == startOfLine(b); } -bool isStartOfLine(const VisiblePosition &p) +bool isStartOfLine(const VisiblePosition& p) { return p.isNotNull() && p == startOfLine(p); } -bool isEndOfLine(const VisiblePosition &p) +bool isEndOfLine(const VisiblePosition& p) { return p.isNotNull() && p == endOfLine(p); } -static inline IntPoint absoluteLineDirectionPointToLocalPointInBlock(RootInlineBox* root, int lineDirectionPoint) +bool isLogicalEndOfLine(const VisiblePosition &p) +{ + return p.isNotNull() && p == logicalEndOfLine(p); +} + +static inline IntPoint absoluteLineDirectionPointToLocalPointInBlock(RootInlineBox& root, int lineDirectionPoint) { - ASSERT(root); - RenderBlockFlow& containingBlock = root->blockFlow(); - FloatPoint absoluteBlockPoint = containingBlock.localToAbsolute(FloatPoint()); - if (containingBlock.hasOverflowClip()) - absoluteBlockPoint -= containingBlock.scrolledContentOffset(); + RenderBlockFlow& containingBlock = root.blockFlow(); + FloatPoint absoluteBlockPoint = containingBlock.localToAbsolute(FloatPoint()) - toFloatSize(containingBlock.scrollPosition()); if (containingBlock.isHorizontalWritingMode()) - return IntPoint(lineDirectionPoint - absoluteBlockPoint.x(), root->blockDirectionPointInLine()); + return IntPoint(lineDirectionPoint - absoluteBlockPoint.x(), root.blockDirectionPointInLine()); - return IntPoint(root->blockDirectionPointInLine(), lineDirectionPoint - absoluteBlockPoint.y()); + return IntPoint(root.blockDirectionPointInLine(), lineDirectionPoint - absoluteBlockPoint.y()); } -VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int lineDirectionPoint, EditableType editableType) +static Element* rootEditableOrDocumentElement(Node& node, EditableType editableType) +{ + if (hasEditableStyle(node, editableType)) + return editableRootForPosition(firstPositionInOrBeforeNode(&node), editableType); + return node.document().documentElement(); +} + +VisiblePosition previousLinePosition(const VisiblePosition& visiblePosition, int lineDirectionPoint, EditableType editableType) { Position p = visiblePosition.deepEquivalent(); Node* node = p.deprecatedNode(); @@ -943,7 +1011,7 @@ VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int if (!renderer) return VisiblePosition(); - RootInlineBox* root = 0; + RootInlineBox* root = nullptr; InlineBox* box; int ignoredCaretOffset; visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); @@ -952,7 +1020,7 @@ VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int // We want to skip zero height boxes. // This could happen in case it is a TrailingFloatsRootInlineBox. if (!root || !root->logicalHeight() || !root->firstLeafChild()) - root = 0; + root = nullptr; } if (!root) { @@ -967,24 +1035,24 @@ VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int if (root) { // FIXME: Can be wrong for multi-column layout and with transforms. - IntPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(root, lineDirectionPoint); + IntPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(*root, lineDirectionPoint); RenderObject& renderer = root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))->renderer(); Node* node = renderer.node(); - if (node && editingIgnoresContent(node)) + if (node && editingIgnoresContent(*node)) return positionInParentBeforeNode(node); - return renderer.positionForPoint(pointInLine); + return renderer.positionForPoint(pointInLine, nullptr); } // Could not find a previous line. This means we must already be on the first line. // Move to the start of the content in this block, which effectively moves us // to the start of the line we're on. - Element* rootElement = node->hasEditableStyle(editableType) ? node->rootEditableElement(editableType) : node->document().documentElement(); + Element* rootElement = rootEditableOrDocumentElement(*node, editableType); if (!rootElement) return VisiblePosition(); return VisiblePosition(firstPositionInNode(rootElement), DOWNSTREAM); } -VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int lineDirectionPoint, EditableType editableType) +VisiblePosition nextLinePosition(const VisiblePosition& visiblePosition, int lineDirectionPoint, EditableType editableType) { Position p = visiblePosition.deepEquivalent(); Node* node = p.deprecatedNode(); @@ -998,7 +1066,7 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int lin if (!renderer) return VisiblePosition(); - RootInlineBox* root = 0; + RootInlineBox* root = nullptr; InlineBox* box; int ignoredCaretOffset; visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); @@ -1007,12 +1075,12 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int lin // We want to skip zero height boxes. // This could happen in case it is a TrailingFloatsRootInlineBox. if (!root || !root->logicalHeight() || !root->firstLeafChild()) - root = 0; + root = nullptr; } if (!root) { // FIXME: We need do the same in previousLinePosition. - Node* child = node->childNode(p.deprecatedEditingOffset()); + Node* child = node->traverseToChildAt(p.deprecatedEditingOffset()); node = child ? child : node->lastDescendant(); Position position = nextRootInlineBoxCandidatePosition(node, visiblePosition, editableType); if (position.isNotNull()) { @@ -1025,18 +1093,18 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int lin if (root) { // FIXME: Can be wrong for multi-column layout and with transforms. - IntPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(root, lineDirectionPoint); + IntPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(*root, lineDirectionPoint); RenderObject& renderer = root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))->renderer(); Node* node = renderer.node(); - if (node && editingIgnoresContent(node)) + if (node && editingIgnoresContent(*node)) return positionInParentBeforeNode(node); - return renderer.positionForPoint(pointInLine); + return renderer.positionForPoint(pointInLine, nullptr); } // Could not find a next line. This means we must already be on the last line. // Move to the end of the content in this block, which effectively moves us // to the end of the line we're on. - Element* rootElement = node->hasEditableStyle(editableType) ? node->rootEditableElement(editableType) : node->document().documentElement(); + Element* rootElement = rootEditableOrDocumentElement(*node, editableType); if (!rootElement) return VisiblePosition(); return VisiblePosition(lastPositionInNode(rootElement), DOWNSTREAM); @@ -1044,76 +1112,55 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int lin // --------- -static unsigned startSentenceBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) +unsigned startSentenceBoundary(StringView text, unsigned, BoundarySearchContextAvailability, bool&) { - TextBreakIterator* iterator = sentenceBreakIterator(StringView(characters, length)); // FIXME: The following function can return -1; we don't handle that. - return textBreakPreceding(iterator, length); + return ubrk_preceding(sentenceBreakIterator(text), text.length()); } -VisiblePosition startOfSentence(const VisiblePosition &c) +VisiblePosition startOfSentence(const VisiblePosition& position) { - return previousBoundary(c, startSentenceBoundary); + return previousBoundary(position, startSentenceBoundary); } -static unsigned endSentenceBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) +unsigned endSentenceBoundary(StringView text, unsigned, BoundarySearchContextAvailability, bool&) { - TextBreakIterator* iterator = sentenceBreakIterator(StringView(characters, length)); - return textBreakNext(iterator); + return ubrk_next(sentenceBreakIterator(text)); } -// FIXME: This includes the space after the punctuation that marks the end of the sentence. -VisiblePosition endOfSentence(const VisiblePosition &c) +VisiblePosition endOfSentence(const VisiblePosition& position) { - return nextBoundary(c, endSentenceBoundary); + // FIXME: This includes the space after the punctuation that marks the end of the sentence. + return nextBoundary(position, endSentenceBoundary); } -static unsigned previousSentencePositionBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) +static unsigned previousSentencePositionBoundary(StringView text, unsigned, BoundarySearchContextAvailability, bool&) { // FIXME: This is identical to startSentenceBoundary. I'm pretty sure that's not right. - TextBreakIterator* iterator = sentenceBreakIterator(StringView(characters, length)); // FIXME: The following function can return -1; we don't handle that. - return textBreakPreceding(iterator, length); + return ubrk_preceding(sentenceBreakIterator(text), text.length()); } -VisiblePosition previousSentencePosition(const VisiblePosition &c) +VisiblePosition previousSentencePosition(const VisiblePosition& position) { - VisiblePosition prev = previousBoundary(c, previousSentencePositionBoundary); - return c.honorEditingBoundaryAtOrBefore(prev); + return position.honorEditingBoundaryAtOrBefore(previousBoundary(position, previousSentencePositionBoundary)); } -static unsigned nextSentencePositionBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) +static unsigned nextSentencePositionBoundary(StringView text, unsigned, BoundarySearchContextAvailability, bool&) { - // FIXME: This is identical to endSentenceBoundary. This isn't right, it needs to - // move to the equivlant position in the following sentence. - TextBreakIterator* iterator = sentenceBreakIterator(StringView(characters, length)); - return textBreakFollowing(iterator, 0); + // FIXME: This is identical to endSentenceBoundary. + // That isn't right. This function needs to move to the equivalent position in the following sentence. + return ubrk_following(sentenceBreakIterator(text), 0); } -VisiblePosition nextSentencePosition(const VisiblePosition &c) +VisiblePosition nextSentencePosition(const VisiblePosition& position) { - VisiblePosition next = nextBoundary(c, nextSentencePositionBoundary); - return c.honorEditingBoundaryAtOrAfter(next); + return position.honorEditingBoundaryAtOrAfter(nextBoundary(position, nextSentencePositionBoundary)); } -VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule) +Node* findStartOfParagraph(Node* startNode, Node* highestRoot, Node* startBlock, int& offset, Position::AnchorType& type, EditingBoundaryCrossingRule boundaryCrossingRule) { - Position p = c.deepEquivalent(); - Node* startNode = p.deprecatedNode(); - - if (!startNode) - return VisiblePosition(); - - if (isRenderedAsNonInlineTableImageOrHR(startNode)) - return positionBeforeNode(startNode); - - Node* startBlock = enclosingBlock(startNode); - Node* node = startNode; - Node* highestRoot = highestEditableRoot(p); - int offset = p.deprecatedEditingOffset(); - Position::AnchorType type = p.anchorType(); - Node* n = startNode; while (n) { #if ENABLE(USERSELECT_ALL) @@ -1124,76 +1171,57 @@ VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossi break; if (boundaryCrossingRule == CanSkipOverEditingBoundary) { while (n && n->hasEditableStyle() != startNode->hasEditableStyle()) - n = NodeTraversal::previousPostOrder(n, startBlock); + n = NodeTraversal::previousPostOrder(*n, startBlock); if (!n || !n->isDescendantOf(highestRoot)) break; } RenderObject* r = n->renderer(); if (!r) { - n = NodeTraversal::previousPostOrder(n, startBlock); + n = NodeTraversal::previousPostOrder(*n, startBlock); continue; } const RenderStyle& style = r->style(); if (style.visibility() != VISIBLE) { - n = NodeTraversal::previousPostOrder(n, startBlock); + n = NodeTraversal::previousPostOrder(*n, startBlock); continue; } if (r->isBR() || isBlock(n)) break; - if (r->isText() && toRenderText(r)->hasRenderedText()) { - ASSERT_WITH_SECURITY_IMPLICATION(n->isTextNode()); + if (is<RenderText>(*r) && downcast<RenderText>(*r).hasRenderedText()) { + ASSERT_WITH_SECURITY_IMPLICATION(is<Text>(*n)); type = Position::PositionIsOffsetInAnchor; if (style.preserveNewline()) { - const UChar* chars = toRenderText(r)->deprecatedCharacters(); - int i = toRenderText(r)->textLength(); + StringImpl& text = *downcast<RenderText>(*r).text(); + int i = text.length(); int o = offset; if (n == startNode && o < i) i = std::max(0, o); while (--i >= 0) { - if (chars[i] == '\n') - return VisiblePosition(Position(toText(n), i + 1), DOWNSTREAM); + if (text[i] == '\n') { + offset = i + 1; + return n; + } } } node = n; offset = 0; - n = NodeTraversal::previousPostOrder(n, startBlock); - } else if (editingIgnoresContent(n) || isTableElement(n)) { + n = NodeTraversal::previousPostOrder(*n, startBlock); + } else if (editingIgnoresContent(*n) || isRenderedTable(n)) { node = n; type = Position::PositionIsBeforeAnchor; - n = n->previousSibling() ? n->previousSibling() : NodeTraversal::previousPostOrder(n, startBlock); + n = n->previousSibling() ? n->previousSibling() : NodeTraversal::previousPostOrder(*n, startBlock); } else - n = NodeTraversal::previousPostOrder(n, startBlock); + n = NodeTraversal::previousPostOrder(*n, startBlock); } - if (type == Position::PositionIsOffsetInAnchor) { - ASSERT(type == Position::PositionIsOffsetInAnchor || !offset); - return VisiblePosition(Position(node, offset, type), DOWNSTREAM); - } - - return VisiblePosition(Position(node, type), DOWNSTREAM); + return node; } -VisiblePosition endOfParagraph(const VisiblePosition &c, EditingBoundaryCrossingRule boundaryCrossingRule) -{ - if (c.isNull()) - return VisiblePosition(); - - Position p = c.deepEquivalent(); - Node* startNode = p.deprecatedNode(); - - if (isRenderedAsNonInlineTableImageOrHR(startNode)) - return positionAfterNode(startNode); - - Node* startBlock = enclosingBlock(startNode); - Node* stayInsideBlock = startBlock; - +Node* findEndOfParagraph(Node* startNode, Node* highestRoot, Node* stayInsideBlock, int& offset, Position::AnchorType& type, EditingBoundaryCrossingRule boundaryCrossingRule) +{ Node* node = startNode; - Node* highestRoot = highestEditableRoot(p); - int offset = p.deprecatedEditingOffset(); - Position::AnchorType type = p.anchorType(); - Node* n = startNode; while (n) { #if ENABLE(USERSELECT_ALL) @@ -1204,49 +1232,107 @@ VisiblePosition endOfParagraph(const VisiblePosition &c, EditingBoundaryCrossing break; if (boundaryCrossingRule == CanSkipOverEditingBoundary) { while (n && n->hasEditableStyle() != startNode->hasEditableStyle()) - n = NodeTraversal::next(n, stayInsideBlock); + n = NodeTraversal::next(*n, stayInsideBlock); if (!n || !n->isDescendantOf(highestRoot)) break; } RenderObject* r = n->renderer(); if (!r) { - n = NodeTraversal::next(n, stayInsideBlock); + n = NodeTraversal::next(*n, stayInsideBlock); continue; } const RenderStyle& style = r->style(); if (style.visibility() != VISIBLE) { - n = NodeTraversal::next(n, stayInsideBlock); + n = NodeTraversal::next(*n, stayInsideBlock); continue; } + // FIXME: This is wrong when startNode is a block. We should return a position after the block. if (r->isBR() || isBlock(n)) break; // FIXME: We avoid returning a position where the renderer can't accept the caret. - if (r->isText() && toRenderText(r)->hasRenderedText()) { - ASSERT_WITH_SECURITY_IMPLICATION(n->isTextNode()); - int length = toRenderText(r)->textLength(); + if (is<RenderText>(*r) && downcast<RenderText>(*r).hasRenderedText()) { + ASSERT_WITH_SECURITY_IMPLICATION(is<Text>(*n)); type = Position::PositionIsOffsetInAnchor; if (style.preserveNewline()) { - const UChar* chars = toRenderText(r)->deprecatedCharacters(); + StringImpl& text = *downcast<RenderText>(*r).text(); int o = n == startNode ? offset : 0; + int length = text.length(); for (int i = o; i < length; ++i) { - if (chars[i] == '\n') - return VisiblePosition(Position(toText(n), i), DOWNSTREAM); + if (text[i] == '\n') { + offset = i; + return n; + } } } node = n; offset = r->caretMaxOffset(); - n = NodeTraversal::next(n, stayInsideBlock); - } else if (editingIgnoresContent(n) || isTableElement(n)) { + n = NodeTraversal::next(*n, stayInsideBlock); + } else if (editingIgnoresContent(*n) || isRenderedTable(n)) { node = n; type = Position::PositionIsAfterAnchor; - n = NodeTraversal::nextSkippingChildren(n, stayInsideBlock); + n = NodeTraversal::nextSkippingChildren(*n, stayInsideBlock); } else - n = NodeTraversal::next(n, stayInsideBlock); + n = NodeTraversal::next(*n, stayInsideBlock); + } + return node; +} + +VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule) +{ + Position p = c.deepEquivalent(); + auto* startNode = p.deprecatedNode(); + + if (!startNode) + return VisiblePosition(); + + if (isRenderedAsNonInlineTableImageOrHR(startNode)) + return positionBeforeNode(startNode); + + Node* startBlock = enclosingBlock(startNode); + + auto* highestRoot = highestEditableRoot(p); + int offset = p.deprecatedEditingOffset(); + Position::AnchorType type = p.anchorType(); + + auto* node = findStartOfParagraph(startNode, highestRoot, startBlock, offset, type, boundaryCrossingRule); + + if (is<Text>(node)) + return VisiblePosition(Position(downcast<Text>(node), offset), DOWNSTREAM); + + if (type == Position::PositionIsOffsetInAnchor) { + ASSERT(type == Position::PositionIsOffsetInAnchor || !offset); + return VisiblePosition(Position(node, offset, type), DOWNSTREAM); } + + return VisiblePosition(Position(node, type), DOWNSTREAM); +} +VisiblePosition endOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule) +{ + if (c.isNull()) + return VisiblePosition(); + + Position p = c.deepEquivalent(); + auto* startNode = p.deprecatedNode(); + + if (isRenderedAsNonInlineTableImageOrHR(startNode)) + return positionAfterNode(startNode); + + auto* startBlock = enclosingBlock(startNode); + auto* stayInsideBlock = startBlock; + + auto* highestRoot = highestEditableRoot(p); + int offset = p.deprecatedEditingOffset(); + Position::AnchorType type = p.anchorType(); + + auto* node = findEndOfParagraph(startNode, highestRoot, stayInsideBlock, offset, type, boundaryCrossingRule); + + if (is<Text>(node)) + return VisiblePosition(Position(downcast<Text>(node), offset), DOWNSTREAM); + if (type == Position::PositionIsOffsetInAnchor) return VisiblePosition(Position(node, offset, type), DOWNSTREAM); @@ -1265,17 +1351,17 @@ VisiblePosition startOfNextParagraph(const VisiblePosition& visiblePosition) return afterParagraphEnd; } -bool inSameParagraph(const VisiblePosition &a, const VisiblePosition &b, EditingBoundaryCrossingRule boundaryCrossingRule) +bool inSameParagraph(const VisiblePosition& a, const VisiblePosition& b, EditingBoundaryCrossingRule boundaryCrossingRule) { return a.isNotNull() && startOfParagraph(a, boundaryCrossingRule) == startOfParagraph(b, boundaryCrossingRule); } -bool isStartOfParagraph(const VisiblePosition &pos, EditingBoundaryCrossingRule boundaryCrossingRule) +bool isStartOfParagraph(const VisiblePosition& pos, EditingBoundaryCrossingRule boundaryCrossingRule) { return pos.isNotNull() && pos == startOfParagraph(pos, boundaryCrossingRule); } -bool isEndOfParagraph(const VisiblePosition &pos, EditingBoundaryCrossingRule boundaryCrossingRule) +bool isEndOfParagraph(const VisiblePosition& pos, EditingBoundaryCrossingRule boundaryCrossingRule) { return pos.isNotNull() && pos == endOfParagraph(pos, boundaryCrossingRule); } @@ -1324,17 +1410,17 @@ VisiblePosition endOfBlock(const VisiblePosition& visiblePosition, EditingBounda return lastPositionInNode(endBlock); } -bool inSameBlock(const VisiblePosition &a, const VisiblePosition &b) +bool inSameBlock(const VisiblePosition& a, const VisiblePosition& b) { return !a.isNull() && enclosingBlock(a.deepEquivalent().containerNode()) == enclosingBlock(b.deepEquivalent().containerNode()); } -bool isStartOfBlock(const VisiblePosition &pos) +bool isStartOfBlock(const VisiblePosition& pos) { return pos.isNotNull() && pos == startOfBlock(pos, CanCrossEditingBoundary); } -bool isEndOfBlock(const VisiblePosition &pos) +bool isEndOfBlock(const VisiblePosition& pos) { return pos.isNotNull() && pos == endOfBlock(pos, CanCrossEditingBoundary); } @@ -1346,21 +1432,16 @@ VisiblePosition startOfDocument(const Node* node) if (!node || !node->document().documentElement()) return VisiblePosition(); -#if PLATFORM(IOS) // The canonicalization of the position at (documentElement, 0) can turn the visible // position to null, even when there's a valid candidate to be had, because the root HTML element // is not content editable. So we construct directly from the valid candidate. - // FIXME: Merge this to Open Source. https://bugs.webkit.org/show_bug.cgi?id=56437 Position firstCandidate = nextCandidate(createLegacyEditingPosition(node->document().documentElement(), 0)); if (firstCandidate.isNull()) return VisiblePosition(); return VisiblePosition(firstCandidate); -#else - return VisiblePosition(firstPositionInNode(node->document().documentElement()), DOWNSTREAM); -#endif } -VisiblePosition startOfDocument(const VisiblePosition &c) +VisiblePosition startOfDocument(const VisiblePosition& c) { return startOfDocument(c.deepEquivalent().deprecatedNode()); } @@ -1370,29 +1451,22 @@ VisiblePosition endOfDocument(const Node* node) if (!node || !node->document().documentElement()) return VisiblePosition(); -#if PLATFORM(IOS) // (As above, in startOfDocument.) The canonicalization can reject valid visible positions // when descending from the root element, so we construct the visible position directly from a // valid candidate. - // FIXME: Merge this to Open Source. https://bugs.webkit.org/show_bug.cgi?id=56437 -#endif - Element* doc = node->document().documentElement(); -#if PLATFORM(IOS) - Position lastPosition = createLegacyEditingPosition(node->document().documentElement(), doc->childNodeCount()); + Position lastPosition = createLegacyEditingPosition(node->document().documentElement(), node->document().documentElement()->countChildNodes()); Position lastCandidate = previousCandidate(lastPosition); if (lastCandidate.isNull()) return VisiblePosition(); return VisiblePosition(lastCandidate); -#endif - return VisiblePosition(lastPositionInNode(doc), DOWNSTREAM); } -VisiblePosition endOfDocument(const VisiblePosition &c) +VisiblePosition endOfDocument(const VisiblePosition& c) { return endOfDocument(c.deepEquivalent().deprecatedNode()); } -bool inSameDocument(const VisiblePosition &a, const VisiblePosition &b) +bool inSameDocument(const VisiblePosition& a, const VisiblePosition& b) { Position ap = a.deepEquivalent(); Node* an = ap.deprecatedNode(); @@ -1406,12 +1480,12 @@ bool inSameDocument(const VisiblePosition &a, const VisiblePosition &b) return &an->document() == &bn->document(); } -bool isStartOfDocument(const VisiblePosition &p) +bool isStartOfDocument(const VisiblePosition& p) { return p.isNotNull() && p.previous(CanCrossEditingBoundary).isNull(); } -bool isEndOfDocument(const VisiblePosition &p) +bool isEndOfDocument(const VisiblePosition& p) { return p.isNotNull() && p.next(CanCrossEditingBoundary).isNull(); } @@ -1420,38 +1494,37 @@ bool isEndOfDocument(const VisiblePosition &p) VisiblePosition startOfEditableContent(const VisiblePosition& visiblePosition) { - Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); + auto* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); if (!highestRoot) - return VisiblePosition(); + return { }; return firstPositionInNode(highestRoot); } VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition) { - Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); + auto* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); if (!highestRoot) - return VisiblePosition(); + return { }; return lastPositionInNode(highestRoot); } -bool isEndOfEditableOrNonEditableContent(const VisiblePosition &p) +bool isEndOfEditableOrNonEditableContent(const VisiblePosition& p) { return p.isNotNull() && p.next().isNull(); } -VisiblePosition leftBoundaryOfLine(const VisiblePosition& c, TextDirection direction) +VisiblePosition leftBoundaryOfLine(const VisiblePosition& c, TextDirection direction, bool* reachedBoundary) { - return direction == LTR ? logicalStartOfLine(c) : logicalEndOfLine(c); + return direction == LTR ? logicalStartOfLine(c, reachedBoundary) : logicalEndOfLine(c, reachedBoundary); } -VisiblePosition rightBoundaryOfLine(const VisiblePosition& c, TextDirection direction) +VisiblePosition rightBoundaryOfLine(const VisiblePosition& c, TextDirection direction, bool* reachedBoundary) { - return direction == LTR ? logicalEndOfLine(c) : logicalStartOfLine(c); + return direction == LTR ? logicalEndOfLine(c, reachedBoundary) : logicalStartOfLine(c, reachedBoundary); } -#if PLATFORM(IOS) static bool directionIsDownstream(SelectionDirection direction) { if (direction == DirectionBackward) @@ -1573,9 +1646,9 @@ bool withinTextUnitOfGranularity(const VisiblePosition& vp, TextGranularity gran return (prevBoundary < vp && vp < nextBoundary); } -static VisiblePosition nextCharacterBoundaryInDirection(const VisiblePosition& vp, SelectionDirection direction) +static VisiblePosition nextCharacterBoundaryInDirection(const VisiblePosition& vp, SelectionDirection direction, EditingBoundaryCrossingRule rule) { - return directionIsDownstream(direction) ? vp.next() : vp.previous(); + return directionIsDownstream(direction) ? vp.next(rule) : vp.previous(rule); } static VisiblePosition nextWordBoundaryInDirection(const VisiblePosition& vp, SelectionDirection direction) @@ -1715,7 +1788,7 @@ VisiblePosition positionOfNextBoundaryOfGranularity(const VisiblePosition& vp, T { switch (granularity) { case CharacterGranularity: - return nextCharacterBoundaryInDirection(vp, direction); + return nextCharacterBoundaryInDirection(vp, direction, CanCrossEditingBoundary); case WordGranularity: return nextWordBoundaryInDirection(vp, direction); case SentenceGranularity: @@ -1732,11 +1805,11 @@ VisiblePosition positionOfNextBoundaryOfGranularity(const VisiblePosition& vp, T } } -PassRefPtr<Range> enclosingTextUnitOfGranularity(const VisiblePosition& vp, TextGranularity granularity, SelectionDirection direction) +RefPtr<Range> enclosingTextUnitOfGranularity(const VisiblePosition& vp, TextGranularity granularity, SelectionDirection direction) { // This is particularly inefficient. We could easily obtain the answer with the boundaries computed below. if (!withinTextUnitOfGranularity(vp, granularity, direction)) - return 0; + return nullptr; VisiblePosition prevBoundary; VisiblePosition nextBoundary; @@ -1788,18 +1861,16 @@ PassRefPtr<Range> enclosingTextUnitOfGranularity(const VisiblePosition& vp, Text default: ASSERT_NOT_REACHED(); - return 0; + return nullptr; } if (prevBoundary.isNull() || nextBoundary.isNull()) - return 0; + return nullptr; if (vp < prevBoundary || vp > nextBoundary) - return 0; - - RefPtr<Range> range = Range::create(prevBoundary.deepEquivalent().deprecatedNode()->document(), prevBoundary, nextBoundary); + return nullptr; - return range; + return Range::create(prevBoundary.deepEquivalent().deprecatedNode()->document(), prevBoundary, nextBoundary); } int distanceBetweenPositions(const VisiblePosition& vp, const VisiblePosition& other) @@ -1818,9 +1889,47 @@ int distanceBetweenPositions(const VisiblePosition& vp, const VisiblePosition& o return (thisIsStart ? -distance : distance); } -PassRefPtr<Range> wordRangeFromPosition(const VisiblePosition& position) +void charactersAroundPosition(const VisiblePosition& position, UChar32& oneAfter, UChar32& oneBefore, UChar32& twoBefore) { - ASSERT(position.isNotNull()); + const int maxCharacters = 3; + Vector<UChar32> characters(maxCharacters); + + if (position.isNull() || isStartOfDocument(position)) + return; + + VisiblePosition startPosition = position; + VisiblePosition endPosition = position; + + VisiblePosition nextPosition = nextCharacterBoundaryInDirection(position, DirectionForward, CannotCrossEditingBoundary); + if (nextPosition.isNotNull()) + endPosition = nextPosition; + + VisiblePosition previousPosition = nextCharacterBoundaryInDirection(position, DirectionBackward, CannotCrossEditingBoundary); + if (previousPosition.isNotNull()) { + startPosition = previousPosition; + previousPosition = nextCharacterBoundaryInDirection(previousPosition, DirectionBackward, CannotCrossEditingBoundary); + if (previousPosition.isNotNull()) + startPosition = previousPosition; + } + + if (startPosition != endPosition) { + String characterString = plainText(Range::create(position.deepEquivalent().anchorNode()->document(), startPosition, endPosition).ptr()).replace(noBreakSpace, ' '); + for (int i = characterString.length() - 1, index = 0; i >= 0 && index < maxCharacters; --i) { + if (!index && nextPosition.isNull()) + index++; + characters[index++] = characterString[i]; + } + } + oneAfter = characters[0]; + oneBefore = characters[1]; + twoBefore = characters[2]; +} + +RefPtr<Range> wordRangeFromPosition(const VisiblePosition& position) +{ + // The selection could be in a non visible element and we don't have a VisiblePosition. + if (position.isNull()) + return nullptr; RefPtr<Range> range = enclosingTextUnitOfGranularity(position, WordGranularity, DirectionBackward); @@ -1888,6 +1997,35 @@ VisiblePosition closestWordBoundaryForPosition(const VisiblePosition& position) return result; } -#endif +RefPtr<Range> rangeExpandedByCharactersInDirectionAtWordBoundary(const VisiblePosition& position, int numberOfCharactersToExpand, SelectionDirection direction) +{ + Position start = position.deepEquivalent(); + Position end = position.deepEquivalent(); + for (int i = 0; i < numberOfCharactersToExpand; ++i) { + if (direction == DirectionBackward) + start = start.previous(Character); + else + end = end.next(Character); + } + + if (direction == DirectionBackward && !atBoundaryOfGranularity(start, WordGranularity, DirectionBackward)) + start = startOfWord(start).deepEquivalent(); + if (direction == DirectionForward && !atBoundaryOfGranularity(end, WordGranularity, DirectionForward)) + end = endOfWord(end).deepEquivalent(); + + return makeRange(start, end); +} + +RefPtr<Range> rangeExpandedAroundPositionByCharacters(const VisiblePosition& position, int numberOfCharactersToExpand) +{ + Position start = position.deepEquivalent(); + Position end = position.deepEquivalent(); + for (int i = 0; i < numberOfCharactersToExpand; ++i) { + start = start.previous(Character); + end = end.next(Character); + } + + return makeRange(start, end); +} } diff --git a/Source/WebCore/editing/VisibleUnits.h b/Source/WebCore/editing/VisibleUnits.h index 0b1e436d9..4ec8bf16f 100644 --- a/Source/WebCore/editing/VisibleUnits.h +++ b/Source/WebCore/editing/VisibleUnits.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2004 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,56 +23,58 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef VisibleUnits_h -#define VisibleUnits_h +#pragma once #include "EditingBoundary.h" -#include "TextDirection.h" +#include "TextFlags.h" #include "VisibleSelection.h" namespace WebCore { class Node; class VisiblePosition; +class SimplifiedBackwardsTextIterator; +class TextIterator; enum EWordSide { RightWordIfOnBoundary = false, LeftWordIfOnBoundary = true }; // words -VisiblePosition startOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBoundary); -VisiblePosition endOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBoundary); -VisiblePosition previousWordPosition(const VisiblePosition &); -VisiblePosition nextWordPosition(const VisiblePosition &); +WEBCORE_EXPORT VisiblePosition startOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBoundary); +WEBCORE_EXPORT VisiblePosition endOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBoundary); +WEBCORE_EXPORT VisiblePosition previousWordPosition(const VisiblePosition &); +WEBCORE_EXPORT VisiblePosition nextWordPosition(const VisiblePosition &); VisiblePosition rightWordPosition(const VisiblePosition&, bool skipsSpaceWhenMovingRight); VisiblePosition leftWordPosition(const VisiblePosition&, bool skipsSpaceWhenMovingRight); bool isStartOfWord(const VisiblePosition&); // sentences -VisiblePosition startOfSentence(const VisiblePosition &); -VisiblePosition endOfSentence(const VisiblePosition &); -VisiblePosition previousSentencePosition(const VisiblePosition &); -VisiblePosition nextSentencePosition(const VisiblePosition &); +WEBCORE_EXPORT VisiblePosition startOfSentence(const VisiblePosition &); +WEBCORE_EXPORT VisiblePosition endOfSentence(const VisiblePosition &); +WEBCORE_EXPORT VisiblePosition previousSentencePosition(const VisiblePosition &); +WEBCORE_EXPORT VisiblePosition nextSentencePosition(const VisiblePosition &); // lines -VisiblePosition startOfLine(const VisiblePosition &); -VisiblePosition endOfLine(const VisiblePosition &); -VisiblePosition previousLinePosition(const VisiblePosition&, int lineDirectionPoint, EditableType = ContentIsEditable); -VisiblePosition nextLinePosition(const VisiblePosition&, int lineDirectionPoint, EditableType = ContentIsEditable); -bool inSameLine(const VisiblePosition &, const VisiblePosition &); -bool isStartOfLine(const VisiblePosition &); -bool isEndOfLine(const VisiblePosition &); -VisiblePosition logicalStartOfLine(const VisiblePosition &); -VisiblePosition logicalEndOfLine(const VisiblePosition &); -VisiblePosition leftBoundaryOfLine(const VisiblePosition&, TextDirection); -VisiblePosition rightBoundaryOfLine(const VisiblePosition&, TextDirection); +WEBCORE_EXPORT VisiblePosition startOfLine(const VisiblePosition &); +WEBCORE_EXPORT VisiblePosition endOfLine(const VisiblePosition &); +WEBCORE_EXPORT VisiblePosition previousLinePosition(const VisiblePosition&, int lineDirectionPoint, EditableType = ContentIsEditable); +WEBCORE_EXPORT VisiblePosition nextLinePosition(const VisiblePosition&, int lineDirectionPoint, EditableType = ContentIsEditable); +WEBCORE_EXPORT bool inSameLine(const VisiblePosition &, const VisiblePosition &); +WEBCORE_EXPORT bool isStartOfLine(const VisiblePosition &); +WEBCORE_EXPORT bool isEndOfLine(const VisiblePosition &); +VisiblePosition logicalStartOfLine(const VisiblePosition &, bool* reachedBoundary = nullptr); +VisiblePosition logicalEndOfLine(const VisiblePosition &, bool* reachedBoundary = nullptr); +bool isLogicalEndOfLine(const VisiblePosition &); +VisiblePosition leftBoundaryOfLine(const VisiblePosition&, TextDirection, bool* reachedBoundary); +VisiblePosition rightBoundaryOfLine(const VisiblePosition&, TextDirection, bool* reachedBoundary); // paragraphs (perhaps a misnomer, can be divided by line break elements) -VisiblePosition startOfParagraph(const VisiblePosition&, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); -VisiblePosition endOfParagraph(const VisiblePosition&, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); +WEBCORE_EXPORT VisiblePosition startOfParagraph(const VisiblePosition&, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); +WEBCORE_EXPORT VisiblePosition endOfParagraph(const VisiblePosition&, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); VisiblePosition startOfNextParagraph(const VisiblePosition&); -VisiblePosition previousParagraphPosition(const VisiblePosition &, int x); -VisiblePosition nextParagraphPosition(const VisiblePosition &, int x); -bool isStartOfParagraph(const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); -bool isEndOfParagraph(const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); +WEBCORE_EXPORT VisiblePosition previousParagraphPosition(const VisiblePosition &, int x); +WEBCORE_EXPORT VisiblePosition nextParagraphPosition(const VisiblePosition &, int x); +WEBCORE_EXPORT bool isStartOfParagraph(const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); +WEBCORE_EXPORT bool isEndOfParagraph(const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); bool inSameParagraph(const VisiblePosition &, const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); // blocks (true paragraphs; line break elements don't break blocks) @@ -83,28 +85,42 @@ bool isStartOfBlock(const VisiblePosition &); bool isEndOfBlock(const VisiblePosition &); // document -VisiblePosition startOfDocument(const Node*); -VisiblePosition endOfDocument(const Node*); -VisiblePosition startOfDocument(const VisiblePosition &); -VisiblePosition endOfDocument(const VisiblePosition &); +WEBCORE_EXPORT VisiblePosition startOfDocument(const Node*); +WEBCORE_EXPORT VisiblePosition endOfDocument(const Node*); +WEBCORE_EXPORT VisiblePosition startOfDocument(const VisiblePosition &); +WEBCORE_EXPORT VisiblePosition endOfDocument(const VisiblePosition &); bool inSameDocument(const VisiblePosition &, const VisiblePosition &); -bool isStartOfDocument(const VisiblePosition &); -bool isEndOfDocument(const VisiblePosition &); +WEBCORE_EXPORT bool isStartOfDocument(const VisiblePosition &); +WEBCORE_EXPORT bool isEndOfDocument(const VisiblePosition &); // editable content -VisiblePosition startOfEditableContent(const VisiblePosition&); -VisiblePosition endOfEditableContent(const VisiblePosition&); -bool isEndOfEditableOrNonEditableContent(const VisiblePosition&); +WEBCORE_EXPORT VisiblePosition startOfEditableContent(const VisiblePosition&); +WEBCORE_EXPORT VisiblePosition endOfEditableContent(const VisiblePosition&); +WEBCORE_EXPORT bool isEndOfEditableOrNonEditableContent(const VisiblePosition&); -#if PLATFORM(IOS) -bool atBoundaryOfGranularity(const VisiblePosition&, TextGranularity, SelectionDirection); -bool withinTextUnitOfGranularity(const VisiblePosition&, TextGranularity, SelectionDirection); -VisiblePosition positionOfNextBoundaryOfGranularity(const VisiblePosition&, TextGranularity, SelectionDirection); -PassRefPtr<Range> enclosingTextUnitOfGranularity(const VisiblePosition&, TextGranularity, SelectionDirection); -int distanceBetweenPositions(const VisiblePosition&, const VisiblePosition&); -PassRefPtr<Range> wordRangeFromPosition(const VisiblePosition& position); -VisiblePosition closestWordBoundaryForPosition(const VisiblePosition& position); -#endif -} // namespace WebCore +WEBCORE_EXPORT bool atBoundaryOfGranularity(const VisiblePosition&, TextGranularity, SelectionDirection); +WEBCORE_EXPORT bool withinTextUnitOfGranularity(const VisiblePosition&, TextGranularity, SelectionDirection); +WEBCORE_EXPORT VisiblePosition positionOfNextBoundaryOfGranularity(const VisiblePosition&, TextGranularity, SelectionDirection); +WEBCORE_EXPORT RefPtr<Range> enclosingTextUnitOfGranularity(const VisiblePosition&, TextGranularity, SelectionDirection); +WEBCORE_EXPORT int distanceBetweenPositions(const VisiblePosition&, const VisiblePosition&); +WEBCORE_EXPORT RefPtr<Range> wordRangeFromPosition(const VisiblePosition&); +WEBCORE_EXPORT VisiblePosition closestWordBoundaryForPosition(const VisiblePosition& position); +WEBCORE_EXPORT void charactersAroundPosition(const VisiblePosition&, UChar32& oneAfter, UChar32& oneBefore, UChar32& twoBefore); +WEBCORE_EXPORT RefPtr<Range> rangeExpandedAroundPositionByCharacters(const VisiblePosition&, int numberOfCharactersToExpand); +WEBCORE_EXPORT RefPtr<Range> rangeExpandedByCharactersInDirectionAtWordBoundary(const VisiblePosition&, int numberOfCharactersToExpand, SelectionDirection); + +// helper function +enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext }; +typedef unsigned (*BoundarySearchFunction)(StringView, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext); +unsigned startWordBoundary(StringView, unsigned, BoundarySearchContextAvailability, bool&); +unsigned endWordBoundary(StringView, unsigned, BoundarySearchContextAvailability, bool&); +unsigned startSentenceBoundary(StringView, unsigned, BoundarySearchContextAvailability, bool&); +unsigned endSentenceBoundary(StringView, unsigned, BoundarySearchContextAvailability, bool&); +unsigned suffixLengthForRange(const Range&, Vector<UChar, 1024>&); +unsigned prefixLengthForRange(const Range&, Vector<UChar, 1024>&); +unsigned backwardSearchForBoundaryWithTextIterator(SimplifiedBackwardsTextIterator&, Vector<UChar, 1024>&, unsigned, BoundarySearchFunction); +unsigned forwardSearchForBoundaryWithTextIterator(TextIterator&, Vector<UChar, 1024>&, unsigned, BoundarySearchFunction); +Node* findStartOfParagraph(Node*, Node*, Node*, int&, Position::AnchorType&, EditingBoundaryCrossingRule); +Node* findEndOfParagraph(Node*, Node*, Node*, int&, Position::AnchorType&, EditingBoundaryCrossingRule); -#endif // VisibleUnits_h +} // namespace WebCore diff --git a/Source/WebCore/editing/WrapContentsInDummySpanCommand.cpp b/Source/WebCore/editing/WrapContentsInDummySpanCommand.cpp index f42d0778e..4006fa8ed 100644 --- a/Source/WebCore/editing/WrapContentsInDummySpanCommand.cpp +++ b/Source/WebCore/editing/WrapContentsInDummySpanCommand.cpp @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -27,28 +27,25 @@ #include "WrapContentsInDummySpanCommand.h" #include "ApplyStyleCommand.h" -#include "ExceptionCodePlaceholder.h" namespace WebCore { -WrapContentsInDummySpanCommand::WrapContentsInDummySpanCommand(PassRefPtr<Element> element) - : SimpleEditCommand(element->document()) +WrapContentsInDummySpanCommand::WrapContentsInDummySpanCommand(Element& element) + : SimpleEditCommand(element.document()) , m_element(element) { - ASSERT(m_element); } void WrapContentsInDummySpanCommand::executeApply() { - Vector<RefPtr<Node>> children; + Vector<Ref<Node>> children; for (Node* child = m_element->firstChild(); child; child = child->nextSibling()) - children.append(child); + children.append(*child); - size_t size = children.size(); - for (size_t i = 0; i < size; ++i) - m_dummySpan->appendChild(children[i].release(), IGNORE_EXCEPTION); + for (auto& child : children) + m_dummySpan->appendChild(child); - m_element->appendChild(m_dummySpan.get(), IGNORE_EXCEPTION); + m_element->appendChild(*m_dummySpan); } void WrapContentsInDummySpanCommand::doApply() @@ -60,26 +57,21 @@ void WrapContentsInDummySpanCommand::doApply() void WrapContentsInDummySpanCommand::doUnapply() { - ASSERT(m_element); - if (!m_dummySpan || !m_element->hasEditableStyle()) return; - Vector<RefPtr<Node>> children; + Vector<Ref<Node>> children; for (Node* child = m_dummySpan->firstChild(); child; child = child->nextSibling()) - children.append(child); + children.append(*child); - size_t size = children.size(); - for (size_t i = 0; i < size; ++i) - m_element->appendChild(children[i].release(), IGNORE_EXCEPTION); + for (auto& child : children) + m_element->appendChild(child); - m_dummySpan->remove(IGNORE_EXCEPTION); + m_dummySpan->remove(); } void WrapContentsInDummySpanCommand::doReapply() -{ - ASSERT(m_element); - +{ if (!m_dummySpan || !m_element->hasEditableStyle()) return; @@ -89,7 +81,7 @@ void WrapContentsInDummySpanCommand::doReapply() #ifndef NDEBUG void WrapContentsInDummySpanCommand::getNodesInCommand(HashSet<Node*>& nodes) { - addNodeAndDescendants(m_element.get(), nodes); + addNodeAndDescendants(m_element.ptr(), nodes); addNodeAndDescendants(m_dummySpan.get(), nodes); } #endif diff --git a/Source/WebCore/editing/WrapContentsInDummySpanCommand.h b/Source/WebCore/editing/WrapContentsInDummySpanCommand.h index 5a9f930fb..fbd0a1d6f 100644 --- a/Source/WebCore/editing/WrapContentsInDummySpanCommand.h +++ b/Source/WebCore/editing/WrapContentsInDummySpanCommand.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef WrapContentsInDummySpanCommand_h -#define WrapContentsInDummySpanCommand_h +#pragma once #include "EditCommand.h" @@ -34,27 +33,25 @@ class HTMLElement; class WrapContentsInDummySpanCommand : public SimpleEditCommand { public: - static PassRefPtr<WrapContentsInDummySpanCommand> create(PassRefPtr<Element> element) + static Ref<WrapContentsInDummySpanCommand> create(Element& element) { - return adoptRef(new WrapContentsInDummySpanCommand(element)); + return adoptRef(*new WrapContentsInDummySpanCommand(element)); } private: - explicit WrapContentsInDummySpanCommand(PassRefPtr<Element>); + explicit WrapContentsInDummySpanCommand(Element&); - virtual void doApply() override; - virtual void doUnapply() override; - virtual void doReapply() override; + void doApply() override; + void doUnapply() override; + void doReapply() override; void executeApply(); #ifndef NDEBUG - virtual void getNodesInCommand(HashSet<Node*>&) override; + void getNodesInCommand(HashSet<Node*>&) override; #endif - RefPtr<Element> m_element; + Ref<Element> m_element; RefPtr<HTMLElement> m_dummySpan; }; } // namespace WebCore - -#endif // WrapContentsInDummySpanCommand_h diff --git a/Source/WebCore/editing/WritingDirection.h b/Source/WebCore/editing/WritingDirection.h index b3cff6668..768b3c25d 100644 --- a/Source/WebCore/editing/WritingDirection.h +++ b/Source/WebCore/editing/WritingDirection.h @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 diff --git a/Source/WebCore/editing/atk/FrameSelectionAtk.cpp b/Source/WebCore/editing/atk/FrameSelectionAtk.cpp index 03a23a7e4..51839a990 100644 --- a/Source/WebCore/editing/atk/FrameSelectionAtk.cpp +++ b/Source/WebCore/editing/atk/FrameSelectionAtk.cpp @@ -25,14 +25,10 @@ #include "AXObjectCache.h" #include "Document.h" #include "Frame.h" +#include "RenderListItem.h" #include "WebKitAccessibleWrapperAtk.h" - -#if PLATFORM(EFL) #include <glib.h> -#else -#include <gtk/gtk.h> -#endif - +#include <wtf/NeverDestroyed.h> #include <wtf/RefPtr.h> namespace WebCore { @@ -43,29 +39,34 @@ static void emitTextSelectionChange(AccessibilityObject* object, VisibleSelectio if (!axObject || !ATK_IS_TEXT(axObject)) return; + // We need to adjust the offset for the list item marker in Left-To-Right text because + // the list item marker is exposed through the text of the accessible list item rather + // than through a separate accessible object. + RenderObject* renderer = object->renderer(); + if (is<RenderListItem>(renderer) && renderer->style().direction() == LTR) + offset += downcast<RenderListItem>(*renderer).markerTextWithSuffix().length(); + g_signal_emit_by_name(axObject, "text-caret-moved", offset); if (selection.isRange()) g_signal_emit_by_name(axObject, "text-selection-changed"); } -static void maybeEmitTextFocusChange(PassRefPtr<AccessibilityObject> prpObject) +static void maybeEmitTextFocusChange(RefPtr<AccessibilityObject>&& object) { // This static variable is needed to keep track of the old object // as per previous calls to this function, in order to properly // decide whether to emit some signals or not. - DEFINE_STATIC_LOCAL(RefPtr<AccessibilityObject>, oldObject, ()); - - RefPtr<AccessibilityObject> object = prpObject; + static NeverDestroyed<RefPtr<AccessibilityObject>> oldObject; // Ensure the oldObject belongs to the same document that the // current object so further comparisons make sense. Otherwise, // just reset oldObject to 0 so it won't be taken into account in // the immediately following call to this function. - if (object && oldObject && oldObject->document() != object->document()) - oldObject = 0; + if (object && oldObject.get() && oldObject.get()->document() != object->document()) + oldObject.get() = nullptr; AtkObject* axObject = object ? object->wrapper() : 0; - AtkObject* oldAxObject = oldObject ? oldObject->wrapper() : 0; + AtkObject* oldAxObject = oldObject.get() ? oldObject.get()->wrapper() : nullptr; if (axObject != oldAxObject) { if (oldAxObject && ATK_IS_TEXT(oldAxObject)) { @@ -79,11 +80,11 @@ static void maybeEmitTextFocusChange(PassRefPtr<AccessibilityObject> prpObject) } // Update pointer to last focused object. - oldObject = object; + oldObject.get() = WTFMove(object); } -void FrameSelection::notifyAccessibilityForSelectionChange() +void FrameSelection::notifyAccessibilityForSelectionChange(const AXTextStateChangeIntent&) { if (!AXObjectCache::accessibilityEnabled()) return; @@ -91,12 +92,15 @@ void FrameSelection::notifyAccessibilityForSelectionChange() if (!m_selection.start().isNotNull() || !m_selection.end().isNotNull()) return; - RenderObject* focusedNode = m_selection.end().containerNode()->renderer(); + Node* focusedNode = m_selection.end().containerNode(); + if (!focusedNode) + return; + AXObjectCache* cache = m_frame->document()->existingAXObjectCache(); if (!cache) return; - AccessibilityObject* accessibilityObject = cache->getOrCreate(focusedNode); + AccessibilityObject* accessibilityObject = cache->getOrCreate(focusedNode->renderer()); if (!accessibilityObject) return; @@ -106,7 +110,7 @@ void FrameSelection::notifyAccessibilityForSelectionChange() return; emitTextSelectionChange(object.get(), m_selection, offset); - maybeEmitTextFocusChange(object.release()); + maybeEmitTextFocusChange(WTFMove(object)); } } // namespace WebCore diff --git a/Source/WebCore/editing/gtk/EditorGtk.cpp b/Source/WebCore/editing/gtk/EditorGtk.cpp new file mode 100644 index 000000000..d16a3fec3 --- /dev/null +++ b/Source/WebCore/editing/gtk/EditorGtk.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2013, 2014 Apple Inc. All rights reserved. + * Copyright (C) 2014 Igalia S.L. + * + * 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 "Editor.h" + +#include "Blob.h" +#include "CachedImage.h" +#include "DOMURL.h" +#include "DocumentFragment.h" +#include "Frame.h" +#include "HTMLEmbedElement.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HTMLObjectElement.h" +#include "HTMLParserIdioms.h" +#include "Pasteboard.h" +#include "RenderImage.h" +#include "SVGElement.h" +#include "SVGImageElement.h" +#include "SelectionData.h" +#include "XLinkNames.h" +#include "markup.h" + +namespace WebCore { + +static RefPtr<DocumentFragment> createFragmentFromPasteboardData(Pasteboard& pasteboard, Frame& frame, Range& range, bool allowPlainText, bool& chosePlainText) +{ + chosePlainText = false; + + if (!pasteboard.hasData()) + return nullptr; + + const auto& selection = pasteboard.selectionData(); + if (selection.hasImage()) { + Vector<uint8_t> buffer; + auto status = cairo_surface_write_to_png_stream(selection.image()->nativeImage().get(), [](void* output, const unsigned char* data, unsigned size) { + if (!reinterpret_cast<Vector<uint8_t>*>(output)->tryAppend(data, size)) + return CAIRO_STATUS_WRITE_ERROR; + return CAIRO_STATUS_SUCCESS; + }, &buffer); + if (status == CAIRO_STATUS_SUCCESS) { + auto blob = Blob::create(WTFMove(buffer), "image/png"); + return frame.editor().createFragmentForImageAndURL(DOMURL::createObjectURL(*frame.document(), blob)); + } + } + + if (selection.hasMarkup() && frame.document()) + return createFragmentFromMarkup(*frame.document(), selection.markup(), emptyString(), DisallowScriptingAndPluginContent); + + if (!allowPlainText) + return nullptr; + + if (selection.hasText()) { + chosePlainText = true; + return createFragmentFromText(range, selection.text()); + } + + return nullptr; +} + +void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText, MailBlockquoteHandling mailBlockquoteHandling) +{ + RefPtr<Range> range = selectedRange(); + if (!range) + return; + + bool chosePlainText; + RefPtr<DocumentFragment> fragment = createFragmentFromPasteboardData(*pasteboard, m_frame, *range, allowPlainText, chosePlainText); + if (fragment && shouldInsertFragment(fragment, range, EditorInsertAction::Pasted)) + pasteAsFragment(fragment.releaseNonNull(), canSmartReplaceWithPasteboard(*pasteboard), chosePlainText, mailBlockquoteHandling); +} + +static const AtomicString& elementURL(Element& element) +{ + if (is<HTMLImageElement>(element) || is<HTMLInputElement>(element)) + return element.attributeWithoutSynchronization(HTMLNames::srcAttr); + if (is<SVGImageElement>(element)) + return element.attributeWithoutSynchronization(XLinkNames::hrefAttr); + if (is<HTMLEmbedElement>(element) || is<HTMLObjectElement>(element)) + return element.imageSourceURL(); + return nullAtom; +} + +static bool getImageForElement(Element& element, RefPtr<Image>& image) +{ + auto* renderer = element.renderer(); + if (!is<RenderImage>(renderer)) + return false; + + CachedImage* cachedImage = downcast<RenderImage>(*renderer).cachedImage(); + if (!cachedImage || cachedImage->errorOccurred()) + return false; + + image = cachedImage->imageForRenderer(renderer); + return image; +} + +void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElement, const URL&, const String& title) +{ + PasteboardImage pasteboardImage; + + if (!getImageForElement(imageElement, pasteboardImage.image)) + return; + ASSERT(pasteboardImage.image); + + pasteboardImage.url.url = imageElement.document().completeURL(stripLeadingAndTrailingHTMLSpaces(elementURL(imageElement))); + pasteboardImage.url.title = title; + pasteboardImage.url.markup = createMarkup(imageElement, IncludeNode, nullptr, ResolveAllURLs); + pasteboard.write(pasteboardImage); +} + +void Editor::writeSelectionToPasteboard(Pasteboard& pasteboard) +{ + PasteboardWebContent pasteboardContent; + pasteboardContent.canSmartCopyOrDelete = canSmartCopyOrDelete(); + pasteboardContent.text = selectedTextForDataTransfer(); + pasteboardContent.markup = createMarkup(*selectedRange(), nullptr, AnnotateForInterchange, false, ResolveNonLocalURLs); + pasteboard.write(pasteboardContent); +} + +RefPtr<DocumentFragment> Editor::webContentFromPasteboard(Pasteboard& pasteboard, Range& context, bool allowPlainText, bool& chosePlainText) +{ + return createFragmentFromPasteboardData(pasteboard, m_frame, context, allowPlainText, chosePlainText); +} + +} // namespace WebCore diff --git a/Source/WebCore/editing/htmlediting.cpp b/Source/WebCore/editing/htmlediting.cpp index 9e721c6da..f40341c9e 100644 --- a/Source/WebCore/editing/htmlediting.cpp +++ b/Source/WebCore/editing/htmlediting.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2004-2007, 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -29,39 +29,49 @@ #include "AXObjectCache.h" #include "Document.h" #include "Editor.h" -#include "ExceptionCodePlaceholder.h" #include "Frame.h" -#include "HTMLBRElement.h" +#include "HTMLBodyElement.h" +#include "HTMLDListElement.h" #include "HTMLDivElement.h" #include "HTMLElementFactory.h" #include "HTMLInterchange.h" #include "HTMLLIElement.h" #include "HTMLNames.h" #include "HTMLOListElement.h" -#include "HTMLObjectElement.h" #include "HTMLParagraphElement.h" +#include "HTMLSpanElement.h" #include "HTMLTableElement.h" #include "HTMLTextFormControlElement.h" #include "HTMLUListElement.h" #include "NodeTraversal.h" #include "PositionIterator.h" +#include "RenderBlock.h" #include "RenderElement.h" +#include "RenderTableCell.h" #include "ShadowRoot.h" #include "Text.h" +#include "TextIterator.h" #include "VisibleUnits.h" #include <wtf/Assertions.h> #include <wtf/StdLibExtras.h> +#include <wtf/text/StringBuilder.h> #include <wtf/unicode/CharacterNames.h> namespace WebCore { using namespace HTMLNames; -// Atomic means that the node has no children, or has children which are ignored for the -// purposes of editing. -bool isAtomicNode(const Node *node) +static bool isVisiblyAdjacent(const Position&, const Position&); + +bool canHaveChildrenForEditing(const Node& node) { - return node && (!node->hasChildNodes() || editingIgnoresContent(node)); + return !is<Text>(node) && node.canContainRangeEndPoint(); +} + +// Atomic means that the node has no children, or has children which are ignored for the purposes of editing. +bool isAtomicNode(const Node* node) +{ + return node && (!node->hasChildNodes() || editingIgnoresContent(*node)); } // Compare two positions, taking into account the possibility that one or both @@ -70,16 +80,15 @@ int comparePositions(const Position& a, const Position& b) { TreeScope* commonScope = commonTreeScope(a.containerNode(), b.containerNode()); - ASSERT(commonScope); if (!commonScope) return 0; - Node* nodeA = commonScope->ancestorInThisScope(a.containerNode()); + Node* nodeA = commonScope->ancestorNodeInThisScope(a.containerNode()); ASSERT(nodeA); bool hasDescendentA = nodeA != a.containerNode(); int offsetA = hasDescendentA ? 0 : a.computeOffsetInContainerNode(); - Node* nodeB = commonScope->ancestorInThisScope(b.containerNode()); + Node* nodeB = commonScope->ancestorNodeInThisScope(b.containerNode()); ASSERT(nodeB); bool hasDescendentB = nodeB != b.containerNode(); int offsetB = hasDescendentB ? 0 : b.computeOffsetInContainerNode(); @@ -92,7 +101,10 @@ int comparePositions(const Position& a, const Position& b) bias = 1; } - int result = Range::compareBoundaryPoints(nodeA, offsetA, nodeB, offsetB, IGNORE_EXCEPTION); + auto comparisonResult = Range::compareBoundaryPoints(nodeA, offsetA, nodeB, offsetB); + if (comparisonResult.hasException()) + return bias; + auto result = comparisonResult.releaseReturnValue(); return result ? result : bias; } @@ -101,202 +113,230 @@ int comparePositions(const VisiblePosition& a, const VisiblePosition& b) return comparePositions(a.deepEquivalent(), b.deepEquivalent()); } -Node* highestEditableRoot(const Position& position, EditableType editableType) +ContainerNode* highestEditableRoot(const Position& position, EditableType editableType) { - Node* node = position.deprecatedNode(); - if (!node) - return 0; - - Node* highestEditableRoot = editableRootForPosition(position, editableType); + ContainerNode* highestEditableRoot = editableRootForPosition(position, editableType); if (!highestEditableRoot) - return 0; + return nullptr; - node = highestEditableRoot; - while (!node->hasTagName(bodyTag)) { + for (ContainerNode* node = highestEditableRoot; !is<HTMLBodyElement>(*node); ) { node = node->parentNode(); if (!node) break; - if (node->hasEditableStyle(editableType)) + // FIXME: Can this ever be a Document or DocumentFragment? If not, this should return Element* instead. + if (hasEditableStyle(*node, editableType)) highestEditableRoot = node; } return highestEditableRoot; } -Node* lowestEditableAncestor(Node* node) +Element* lowestEditableAncestor(Node* node) { - if (!node) - return 0; - - while (node) { + for (; node; node = node->parentNode()) { if (node->hasEditableStyle()) return node->rootEditableElement(); - if (node->hasTagName(bodyTag)) + if (is<HTMLBodyElement>(*node)) break; - node = node->parentNode(); } - - return 0; + return nullptr; } -bool isEditablePosition(const Position& p, EditableType editableType, EUpdateStyle updateStyle) +static bool isEditableToAccessibility(const Node& node) { - Node* node = p.deprecatedNode(); - if (!node) + ASSERT(AXObjectCache::accessibilityEnabled()); + ASSERT(node.document().existingAXObjectCache()); + + if (auto* cache = node.document().existingAXObjectCache()) + return cache->rootAXEditableElement(&node); + + return false; +} + +static bool computeEditability(const Node& node, EditableType editableType, Node::ShouldUpdateStyle shouldUpdateStyle) +{ + if (node.computeEditability(Node::UserSelectAllIsAlwaysNonEditable, shouldUpdateStyle) != Node::Editability::ReadOnly) + return true; + + switch (editableType) { + case ContentIsEditable: return false; - if (updateStyle == UpdateStyle) - node->document().updateLayoutIgnorePendingStylesheets(); - else - ASSERT(updateStyle == DoNotUpdateStyle); + case HasEditableAXRole: + return isEditableToAccessibility(node); + } + ASSERT_NOT_REACHED(); + return false; +} - if (node->renderer() && node->renderer()->isTable()) - node = node->parentNode(); - - return node->hasEditableStyle(editableType); +bool hasEditableStyle(const Node& node, EditableType editableType) +{ + return computeEditability(node, editableType, Node::ShouldUpdateStyle::DoNotUpdate); } -bool isAtUnsplittableElement(const Position& pos) +bool isEditableNode(const Node& node) { - Node* node = pos.deprecatedNode(); - return (node == editableRootForPosition(pos) || node == enclosingNodeOfType(pos, &isTableCell)); + return computeEditability(node, ContentIsEditable, Node::ShouldUpdateStyle::Update); } - - -bool isRichlyEditablePosition(const Position& p, EditableType editableType) + +bool isEditablePosition(const Position& position, EditableType editableType) { - Node* node = p.deprecatedNode(); - if (!node) - return false; - - if (node->renderer() && node->renderer()->isTable()) - node = node->parentNode(); - - return node->hasRichlyEditableStyle(editableType); + Node* node = position.containerNode(); + return node && computeEditability(*node, editableType, Node::ShouldUpdateStyle::Update); } -Element* editableRootForPosition(const Position& p, EditableType editableType) +bool isAtUnsplittableElement(const Position& position) { - Node* node = p.containerNode(); + Node* node = position.containerNode(); + return node == editableRootForPosition(position) || node == enclosingNodeOfType(position, isTableCell); +} + +bool isRichlyEditablePosition(const Position& position) +{ + auto* node = position.containerNode(); + return node && node->hasRichlyEditableStyle(); +} + +Element* editableRootForPosition(const Position& position, EditableType editableType) +{ + Node* node = position.containerNode(); if (!node) - return 0; - - if (node->renderer() && node->renderer()->isTable()) - node = node->parentNode(); - - return node->rootEditableElement(editableType); + return nullptr; + + switch (editableType) { + case HasEditableAXRole: + if (auto* cache = node->document().existingAXObjectCache()) + return const_cast<Element*>(cache->rootAXEditableElement(node)); + FALLTHROUGH; + case ContentIsEditable: + return node->rootEditableElement(); + } + return nullptr; } // Finds the enclosing element until which the tree can be split. // When a user hits ENTER, he/she won't expect this element to be split into two. // You may pass it as the second argument of splitTreeToNode. -Element* unsplittableElementForPosition(const Position& p) +Element* unsplittableElementForPosition(const Position& position) { // Since enclosingNodeOfType won't search beyond the highest root editable node, // this code works even if the closest table cell was outside of the root editable node. - Element* enclosingCell = toElement(enclosingNodeOfType(p, &isTableCell)); - if (enclosingCell) + if (auto* enclosingCell = downcast<Element>(enclosingNodeOfType(position, &isTableCell))) return enclosingCell; - - return editableRootForPosition(p); + return editableRootForPosition(position); } Position nextCandidate(const Position& position) { - PositionIterator p = position; - while (!p.atEnd()) { - p.increment(); - if (p.isCandidate()) - return p; + for (PositionIterator nextPosition = position; !nextPosition.atEnd(); ) { + nextPosition.increment(); + if (nextPosition.isCandidate()) + return nextPosition; } - return Position(); + return { }; } Position nextVisuallyDistinctCandidate(const Position& position) { - Position p = position; - Position downstreamStart = p.downstream(); - while (!p.atEndOfTree()) { - p = p.next(Character); - if (p.isCandidate() && p.downstream() != downstreamStart) - return p; + // FIXME: Use PositionIterator instead. + Position nextPosition = position; + Position downstreamStart = nextPosition.downstream(); + while (!nextPosition.atEndOfTree()) { + nextPosition = nextPosition.next(Character); + if (nextPosition.isCandidate() && nextPosition.downstream() != downstreamStart) + return nextPosition; + if (auto* node = nextPosition.containerNode()) { + if (!node->renderer()) + nextPosition = lastPositionInOrAfterNode(node); + } } - return Position(); + return { }; } Position previousCandidate(const Position& position) { - PositionIterator p = position; - while (!p.atStart()) { - p.decrement(); - if (p.isCandidate()) - return p; + PositionIterator previousPosition = position; + while (!previousPosition.atStart()) { + previousPosition.decrement(); + if (previousPosition.isCandidate()) + return previousPosition; } - return Position(); + return { }; } Position previousVisuallyDistinctCandidate(const Position& position) { - Position p = position; - Position downstreamStart = p.downstream(); - while (!p.atStartOfTree()) { - p = p.previous(Character); - if (p.isCandidate() && p.downstream() != downstreamStart) - return p; + // FIXME: Use PositionIterator instead. + Position previousPosition = position; + Position downstreamStart = previousPosition.downstream(); + while (!previousPosition.atStartOfTree()) { + previousPosition = previousPosition.previous(Character); + if (previousPosition.isCandidate() && previousPosition.downstream() != downstreamStart) + return previousPosition; + if (auto* node = previousPosition.containerNode()) { + if (!node->renderer()) + previousPosition = firstPositionInOrBeforeNode(node); + } } - return Position(); + return { }; } -VisiblePosition firstEditablePositionAfterPositionInRoot(const Position& position, Node* highestRoot) +Position firstEditablePositionAfterPositionInRoot(const Position& position, ContainerNode* highestRoot) { + if (!highestRoot) + return { }; + // position falls before highestRoot. if (comparePositions(position, firstPositionInNode(highestRoot)) == -1 && highestRoot->hasEditableStyle()) return firstPositionInNode(highestRoot); - Position p = position; + Position candidate = position; if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) { - Node* shadowAncestor = highestRoot->treeScope().ancestorInThisScope(p.deprecatedNode()); + auto* shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode()); if (!shadowAncestor) - return VisiblePosition(); + return { }; - p = positionAfterNode(shadowAncestor); + candidate = positionAfterNode(shadowAncestor); } - while (p.deprecatedNode() && !isEditablePosition(p) && p.deprecatedNode()->isDescendantOf(highestRoot)) - p = isAtomicNode(p.deprecatedNode()) ? positionInParentAfterNode(p.deprecatedNode()) : nextVisuallyDistinctCandidate(p); - - if (p.deprecatedNode() && p.deprecatedNode() != highestRoot && !p.deprecatedNode()->isDescendantOf(highestRoot)) - return VisiblePosition(); - - return VisiblePosition(p); + while (candidate.deprecatedNode() && !isEditablePosition(candidate) && candidate.deprecatedNode()->isDescendantOf(*highestRoot)) + candidate = isAtomicNode(candidate.deprecatedNode()) ? positionInParentAfterNode(candidate.deprecatedNode()) : nextVisuallyDistinctCandidate(candidate); + + if (candidate.deprecatedNode() && candidate.deprecatedNode() != highestRoot && !candidate.deprecatedNode()->isDescendantOf(*highestRoot)) + return { }; + + return candidate; } -VisiblePosition lastEditablePositionBeforePositionInRoot(const Position& position, Node* highestRoot) +Position lastEditablePositionBeforePositionInRoot(const Position& position, ContainerNode* highestRoot) { + if (!highestRoot) + return { }; + // When position falls after highestRoot, the result is easy to compute. if (comparePositions(position, lastPositionInNode(highestRoot)) == 1) return lastPositionInNode(highestRoot); - Position p = position; + Position candidate = position; if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) { - Node* shadowAncestor = highestRoot->treeScope().ancestorInThisScope(p.deprecatedNode()); + auto* shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode()); if (!shadowAncestor) - return VisiblePosition(); + return { }; - p = firstPositionInOrBeforeNode(shadowAncestor); + candidate = firstPositionInOrBeforeNode(shadowAncestor); } + + while (candidate.deprecatedNode() && !isEditablePosition(candidate) && candidate.deprecatedNode()->isDescendantOf(*highestRoot)) + candidate = isAtomicNode(candidate.deprecatedNode()) ? positionInParentBeforeNode(candidate.deprecatedNode()) : previousVisuallyDistinctCandidate(candidate); - while (p.deprecatedNode() && !isEditablePosition(p) && p.deprecatedNode()->isDescendantOf(highestRoot)) - p = isAtomicNode(p.deprecatedNode()) ? positionInParentBeforeNode(p.deprecatedNode()) : previousVisuallyDistinctCandidate(p); - - if (p.deprecatedNode() && p.deprecatedNode() != highestRoot && !p.deprecatedNode()->isDescendantOf(highestRoot)) - return VisiblePosition(); + if (candidate.deprecatedNode() && candidate.deprecatedNode() != highestRoot && !candidate.deprecatedNode()->isDescendantOf(*highestRoot)) + return { }; - return VisiblePosition(p); + return candidate; } -// FIXME: The method name, comment, and code say three different things here! +// FIXME: The function name, comment, and code say three different things here! // Whether or not content before and after this node will collapse onto the same line as it. bool isBlock(const Node* node) { @@ -315,7 +355,7 @@ bool isInline(const Node* node) Element* enclosingBlock(Node* node, EditingBoundaryCrossingRule rule) { Node* enclosingNode = enclosingNodeOfType(firstPositionInOrBeforeNode(node), isBlock, rule); - return enclosingNode && enclosingNode->isElementNode() ? toElement(enclosingNode) : 0; + return is<Element>(enclosingNode) ? downcast<Element>(enclosingNode) : nullptr; } TextDirection directionOfEnclosingBlock(const Position& position) @@ -333,77 +373,89 @@ TextDirection directionOfEnclosingBlock(const Position& position) // in a node. It returns 1 for some elements even though they do not have children, which // creates technically invalid DOM Positions. Be sure to call parentAnchoredEquivalent // on a Position before using it to create a DOM Range, or an exception will be thrown. -int lastOffsetForEditing(const Node* node) +int lastOffsetForEditing(const Node& node) { - ASSERT(node); - if (!node) - return 0; - if (node->offsetInCharacters()) - return node->maxCharacterOffset(); + if (node.offsetInCharacters()) + return node.maxCharacterOffset(); - if (node->hasChildNodes()) - return node->childNodeCount(); + if (node.hasChildNodes()) + return node.countChildNodes(); - // NOTE: This should preempt the childNodeCount for, e.g., select nodes + // NOTE: This should preempt the countChildNodes() for, e.g., select nodes. + // FIXME: What does the comment above mean? if (editingIgnoresContent(node)) return 1; return 0; } +bool isAmbiguousBoundaryCharacter(UChar character) +{ + // These are characters that can behave as word boundaries, but can appear within words. + // If they are just typed, i.e. if they are immediately followed by a caret, we want to delay text checking until the next character has been typed. + // FIXME: this is required until <rdar://problem/6853027> is fixed and text checking can do this for us. + return character == '\'' || character == '@' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim; +} + String stringWithRebalancedWhitespace(const String& string, bool startIsStartOfParagraph, bool endIsEndOfParagraph) { - Vector<UChar> rebalancedString; - append(rebalancedString, string); + StringBuilder rebalancedString; bool previousCharacterWasSpace = false; - for (size_t i = 0; i < rebalancedString.size(); i++) { - if (!isWhitespace(rebalancedString[i])) { + unsigned length = string.length(); + for (unsigned i = 0; i < length; ++i) { + auto character = string[i]; + if (!deprecatedIsEditingWhitespace(character)) { previousCharacterWasSpace = false; continue; } - - if (previousCharacterWasSpace || (!i && startIsStartOfParagraph) || (i + 1 == rebalancedString.size() && endIsEndOfParagraph)) { - rebalancedString[i] = noBreakSpace; + LChar selectedWhitespaceCharacter; + if (previousCharacterWasSpace || (!i && startIsStartOfParagraph) || (i == length - 1 && endIsEndOfParagraph)) { + selectedWhitespaceCharacter = noBreakSpace; previousCharacterWasSpace = false; } else { - rebalancedString[i] = ' '; + selectedWhitespaceCharacter = ' '; previousCharacterWasSpace = true; } - + if (character == selectedWhitespaceCharacter) + continue; + rebalancedString.reserveCapacity(length); + rebalancedString.append(string, rebalancedString.length(), i - rebalancedString.length()); + rebalancedString.append(selectedWhitespaceCharacter); } - return String::adopt(rebalancedString); + if (rebalancedString.isEmpty()) + return string; + + rebalancedString.reserveCapacity(length); + rebalancedString.append(string, rebalancedString.length(), length - rebalancedString.length()); + return rebalancedString.toString(); } -bool isTableStructureNode(const Node *node) +bool isTableStructureNode(const Node* node) { - RenderObject* renderer = node->renderer(); - return (renderer && (renderer->isTableCell() || renderer->isTableRow() || renderer->isTableSection() || renderer->isRenderTableCol())); + auto* renderer = node->renderer(); + return renderer && (renderer->isTableCell() || renderer->isTableRow() || renderer->isTableSection() || renderer->isRenderTableCol()); } const String& nonBreakingSpaceString() { - DEFINE_STATIC_LOCAL(String, nonBreakingSpaceString, (&noBreakSpace, 1)); + static NeverDestroyed<String> nonBreakingSpaceString(&noBreakSpace, 1); return nonBreakingSpaceString; } -// FIXME: need to dump this -bool isSpecialElement(const Node *n) +static bool isSpecialHTMLElement(const Node* node) { - if (!n) - return false; - - if (!n->isHTMLElement()) + if (!is<HTMLElement>(node)) return false; - if (n->isLink()) + if (downcast<HTMLElement>(*node).isLink()) return true; - RenderObject* renderer = n->renderer(); + auto* renderer = downcast<HTMLElement>(*node).renderer(); if (!renderer) return false; - + if (renderer->style().display() == TABLE || renderer->style().display() == INLINE_TABLE) return true; @@ -412,162 +464,140 @@ bool isSpecialElement(const Node *n) if (renderer->style().position() != StaticPosition) return true; - - return false; -} -static Node* firstInSpecialElement(const Position& pos) -{ - Node* rootEditableElement = pos.containerNode()->rootEditableElement(); - for (Node* n = pos.deprecatedNode(); n && n->rootEditableElement() == rootEditableElement; n = n->parentNode()) - if (isSpecialElement(n)) { - VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM); - VisiblePosition firstInElement = VisiblePosition(firstPositionInOrBeforeNode(n), DOWNSTREAM); - if (isTableElement(n) && vPos == firstInElement.next()) - return n; - if (vPos == firstInElement) - return n; - } - return 0; + return false; } -static Node* lastInSpecialElement(const Position& pos) +static HTMLElement* firstInSpecialElement(const Position& position) { - Node* rootEditableElement = pos.containerNode()->rootEditableElement(); - for (Node* n = pos.deprecatedNode(); n && n->rootEditableElement() == rootEditableElement; n = n->parentNode()) - if (isSpecialElement(n)) { - VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM); - VisiblePosition lastInElement = VisiblePosition(lastPositionInOrAfterNode(n), DOWNSTREAM); - if (isTableElement(n) && vPos == lastInElement.previous()) - return n; - if (vPos == lastInElement) - return n; - } - return 0; + auto* rootEditableElement = position.containerNode()->rootEditableElement(); + for (Node* node = position.deprecatedNode(); node && node->rootEditableElement() == rootEditableElement; node = node->parentNode()) { + if (!isSpecialHTMLElement(node)) + continue; + VisiblePosition vPos(position, DOWNSTREAM); + VisiblePosition firstInElement(firstPositionInOrBeforeNode(node), DOWNSTREAM); + if ((isRenderedTable(node) && vPos == firstInElement.next()) || vPos == firstInElement) + return &downcast<HTMLElement>(*node); + } + return nullptr; } -bool isFirstVisiblePositionInSpecialElement(const Position& pos) +static HTMLElement* lastInSpecialElement(const Position& position) { - return firstInSpecialElement(pos); + auto* rootEditableElement = position.containerNode()->rootEditableElement(); + for (Node* node = position.deprecatedNode(); node && node->rootEditableElement() == rootEditableElement; node = node->parentNode()) { + if (!isSpecialHTMLElement(node)) + continue; + VisiblePosition vPos(position, DOWNSTREAM); + VisiblePosition lastInElement(lastPositionInOrAfterNode(node), DOWNSTREAM); + if ((isRenderedTable(node) && vPos == lastInElement.previous()) || vPos == lastInElement) + return &downcast<HTMLElement>(*node); + } + return nullptr; } -Position positionBeforeContainingSpecialElement(const Position& pos, Node** containingSpecialElement) +Position positionBeforeContainingSpecialElement(const Position& position, HTMLElement** containingSpecialElement) { - Node* n = firstInSpecialElement(pos); - if (!n) - return pos; - Position result = positionInParentBeforeNode(n); - if (result.isNull() || result.deprecatedNode()->rootEditableElement() != pos.deprecatedNode()->rootEditableElement()) - return pos; + auto* element = firstInSpecialElement(position); + if (!element) + return position; + Position result = positionInParentBeforeNode(element); + if (result.isNull() || result.deprecatedNode()->rootEditableElement() != position.deprecatedNode()->rootEditableElement()) + return position; if (containingSpecialElement) - *containingSpecialElement = n; + *containingSpecialElement = element; return result; } -bool isLastVisiblePositionInSpecialElement(const Position& pos) -{ - return lastInSpecialElement(pos); -} - -Position positionAfterContainingSpecialElement(const Position& pos, Node **containingSpecialElement) +Position positionAfterContainingSpecialElement(const Position& position, HTMLElement** containingSpecialElement) { - Node* n = lastInSpecialElement(pos); - if (!n) - return pos; - Position result = positionInParentAfterNode(n); - if (result.isNull() || result.deprecatedNode()->rootEditableElement() != pos.deprecatedNode()->rootEditableElement()) - return pos; + auto* element = lastInSpecialElement(position); + if (!element) + return position; + Position result = positionInParentAfterNode(element); + if (result.isNull() || result.deprecatedNode()->rootEditableElement() != position.deprecatedNode()->rootEditableElement()) + return position; if (containingSpecialElement) - *containingSpecialElement = n; + *containingSpecialElement = element; return result; } -Position positionOutsideContainingSpecialElement(const Position &pos, Node **containingSpecialElement) -{ - if (isFirstVisiblePositionInSpecialElement(pos)) - return positionBeforeContainingSpecialElement(pos, containingSpecialElement); - if (isLastVisiblePositionInSpecialElement(pos)) - return positionAfterContainingSpecialElement(pos, containingSpecialElement); - return pos; -} - -Node* isFirstPositionAfterTable(const VisiblePosition& visiblePosition) +Element* isFirstPositionAfterTable(const VisiblePosition& position) { - Position upstream(visiblePosition.deepEquivalent().upstream()); - if (upstream.deprecatedNode() && upstream.deprecatedNode()->renderer() && upstream.deprecatedNode()->renderer()->isTable() && upstream.atLastEditingPositionForNode()) - return upstream.deprecatedNode(); - - return 0; + Position upstream(position.deepEquivalent().upstream()); + auto* node = upstream.deprecatedNode(); + if (!node) + return nullptr; + auto* renderer = node->renderer(); + if (!renderer || !renderer->isTable() || !upstream.atLastEditingPositionForNode()) + return nullptr; + return &downcast<Element>(*node); } -Node* isLastPositionBeforeTable(const VisiblePosition& visiblePosition) +Element* isLastPositionBeforeTable(const VisiblePosition& position) { - Position downstream(visiblePosition.deepEquivalent().downstream()); - if (downstream.deprecatedNode() && downstream.deprecatedNode()->renderer() && downstream.deprecatedNode()->renderer()->isTable() && downstream.atFirstEditingPositionForNode()) - return downstream.deprecatedNode(); - - return 0; + Position downstream(position.deepEquivalent().downstream()); + auto* node = downstream.deprecatedNode(); + if (!node) + return nullptr; + auto* renderer = node->renderer(); + if (!renderer || !renderer->isTable() || !downstream.atFirstEditingPositionForNode()) + return nullptr; + return &downcast<Element>(*node); } // Returns the visible position at the beginning of a node -VisiblePosition visiblePositionBeforeNode(Node* node) +VisiblePosition visiblePositionBeforeNode(Node& node) { - ASSERT(node); - if (node->childNodeCount()) - return VisiblePosition(firstPositionInOrBeforeNode(node), DOWNSTREAM); - ASSERT(node->parentNode()); - ASSERT(!node->parentNode()->isShadowRoot()); - return positionInParentBeforeNode(node); + if (node.hasChildNodes()) + return VisiblePosition(firstPositionInOrBeforeNode(&node), DOWNSTREAM); + ASSERT(node.parentNode()); + ASSERT(!node.parentNode()->isShadowRoot()); + return positionInParentBeforeNode(&node); } // Returns the visible position at the ending of a node -VisiblePosition visiblePositionAfterNode(Node* node) +VisiblePosition visiblePositionAfterNode(Node& node) { - ASSERT(node); - if (node->childNodeCount()) - return VisiblePosition(lastPositionInOrAfterNode(node), DOWNSTREAM); - ASSERT(node->parentNode()); - ASSERT(!node->parentNode()->isShadowRoot()); - return positionInParentAfterNode(node); + if (node.hasChildNodes()) + return VisiblePosition(lastPositionInOrAfterNode(&node), DOWNSTREAM); + ASSERT(node.parentNode()); + ASSERT(!node.parentNode()->isShadowRoot()); + return positionInParentAfterNode(&node); } -bool isListElement(Node *n) +bool isListHTMLElement(Node* node) { - return (n && (n->hasTagName(ulTag) || n->hasTagName(olTag) || n->hasTagName(dlTag))); + return node && (is<HTMLUListElement>(*node) || is<HTMLOListElement>(*node) || is<HTMLDListElement>(*node)); } -bool isListItem(const Node *n) +bool isListItem(const Node* node) { - return n && (isListElement(n->parentNode()) || (n->renderer() && n->renderer()->isListItem())); + return node && (isListHTMLElement(node->parentNode()) || (node->renderer() && node->renderer()->isListItem())); } -Node* enclosingNodeWithTag(const Position& p, const QualifiedName& tagName) +Element* enclosingElementWithTag(const Position& position, const QualifiedName& tagName) { - if (p.isNull()) - return 0; - - Node* root = highestEditableRoot(p); - for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) { - if (root && !n->hasEditableStyle()) + auto* root = highestEditableRoot(position); + for (Node* node = position.deprecatedNode(); node; node = node->parentNode()) { + if (root && !node->hasEditableStyle()) continue; - if (n->hasTagName(tagName)) - return n; - if (n == root) - return 0; + if (!is<Element>(*node)) + continue; + if (downcast<Element>(*node).hasTagName(tagName)) + return &downcast<Element>(*node); + if (node == root) + return nullptr; } - - return 0; + return nullptr; } -Node* enclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule) +Node* enclosingNodeOfType(const Position& position, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule) { // FIXME: support CanSkipCrossEditingBoundary ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary); - if (p.isNull()) - return 0; - - Node* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(p) : 0; - for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) { + auto* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(position) : nullptr; + for (Node* n = position.deprecatedNode(); n; n = n->parentNode()) { // Don't return a non-editable node if the input position was editable, since // the callers from editing will no doubt want to perform editing inside the returned node. if (root && !n->hasEditableStyle()) @@ -575,17 +605,16 @@ Node* enclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), if (nodeIsOfType(n)) return n; if (n == root) - return 0; + return nullptr; } - - return 0; + return nullptr; } -Node* highestEnclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule, Node* stayWithin) +Node* highestEnclosingNodeOfType(const Position& position, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule, Node* stayWithin) { - Node* highest = 0; - Node* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(p) : 0; - for (Node* n = p.containerNode(); n && n != stayWithin; n = n->parentNode()) { + Node* highest = nullptr; + auto* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(position) : nullptr; + for (Node* n = position.containerNode(); n && n != stayWithin; n = n->parentNode()) { if (root && !n->hasEditableStyle()) continue; if (nodeIsOfType(n)) @@ -593,7 +622,6 @@ Node* highestEnclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const N if (n == root) break; } - return highest; } @@ -601,43 +629,40 @@ static bool hasARenderedDescendant(Node* node, Node* excludedNode) { for (Node* n = node->firstChild(); n;) { if (n == excludedNode) { - n = NodeTraversal::nextSkippingChildren(n, node); + n = NodeTraversal::nextSkippingChildren(*n, node); continue; } if (n->renderer()) return true; - n = NodeTraversal::next(n, node); + n = NodeTraversal::next(*n, node); } return false; } Node* highestNodeToRemoveInPruning(Node* node) { - Node* previousNode = 0; - Node* rootEditableElement = node ? node->rootEditableElement() : 0; + Node* previousNode = nullptr; + auto* rootEditableElement = node ? node->rootEditableElement() : nullptr; for (; node; node = node->parentNode()) { - if (RenderObject* renderer = node->renderer()) { + if (auto* renderer = node->renderer()) { if (!renderer->canHaveChildren() || hasARenderedDescendant(node, previousNode) || rootEditableElement == node) return previousNode; } previousNode = node; } - return 0; + return nullptr; } -Node* enclosingTableCell(const Position& p) +Element* enclosingTableCell(const Position& position) { - return toElement(enclosingNodeOfType(p, isTableCell)); + return downcast<Element>(enclosingNodeOfType(position, isTableCell)); } Element* enclosingAnchorElement(const Position& p) { - if (p.isNull()) - return nullptr; - for (Node* node = p.deprecatedNode(); node; node = node->parentNode()) { - if (node->isElementNode() && node->isLink()) - return toElement(node); + if (is<Element>(*node) && node->isLink()) + return downcast<Element>(node); } return nullptr; } @@ -645,90 +670,90 @@ Element* enclosingAnchorElement(const Position& p) HTMLElement* enclosingList(Node* node) { if (!node) - return 0; + return nullptr; - Node* root = highestEditableRoot(firstPositionInOrBeforeNode(node)); + auto* root = highestEditableRoot(firstPositionInOrBeforeNode(node)); - for (ContainerNode* n = node->parentNode(); n; n = n->parentNode()) { - if (n->hasTagName(ulTag) || n->hasTagName(olTag)) - return toHTMLElement(n); - if (n == root) - return 0; + for (ContainerNode* ancestor = node->parentNode(); ancestor; ancestor = ancestor->parentNode()) { + if (is<HTMLUListElement>(*ancestor) || is<HTMLOListElement>(*ancestor)) + return downcast<HTMLElement>(ancestor); + if (ancestor == root) + return nullptr; } - return 0; + return nullptr; } Node* enclosingListChild(Node *node) { if (!node) - return 0; + return nullptr; + // Check for a list item element, or for a node whose parent is a list element. Such a node // will appear visually as a list item (but without a list marker) - Node* root = highestEditableRoot(firstPositionInOrBeforeNode(node)); + auto* root = highestEditableRoot(firstPositionInOrBeforeNode(node)); - // FIXME: This function is inappropriately named if it starts with node instead of node->parentNode() + // FIXME: This function is inappropriately named since it starts with node instead of node->parentNode() for (Node* n = node; n && n->parentNode(); n = n->parentNode()) { - if (n->hasTagName(liTag) || (isListElement(n->parentNode()) && n != root)) + if (is<HTMLLIElement>(*n) || (isListHTMLElement(n->parentNode()) && n != root)) return n; if (n == root || isTableCell(n)) - return 0; + return nullptr; } - - return 0; + + return nullptr; } static HTMLElement* embeddedSublist(Node* listItem) { // Check the DOM so that we'll find collapsed sublists without renderers. for (Node* n = listItem->firstChild(); n; n = n->nextSibling()) { - if (isListElement(n)) - return toHTMLElement(n); + if (isListHTMLElement(n)) + return downcast<HTMLElement>(n); } - - return 0; + return nullptr; } static Node* appendedSublist(Node* listItem) { // Check the DOM so that we'll find collapsed sublists without renderers. for (Node* n = listItem->nextSibling(); n; n = n->nextSibling()) { - if (isListElement(n)) - return toHTMLElement(n); + if (isListHTMLElement(n)) + return downcast<HTMLElement>(n); if (isListItem(listItem)) - return 0; + return nullptr; } - return 0; + return nullptr; } -// FIXME: This method should not need to call isStartOfParagraph/isEndOfParagraph -Node* enclosingEmptyListItem(const VisiblePosition& visiblePos) +// FIXME: This function should not need to call isStartOfParagraph/isEndOfParagraph. +Node* enclosingEmptyListItem(const VisiblePosition& position) { // Check that position is on a line by itself inside a list item - Node* listChildNode = enclosingListChild(visiblePos.deepEquivalent().deprecatedNode()); - if (!listChildNode || !isStartOfParagraph(visiblePos) || !isEndOfParagraph(visiblePos)) - return 0; + auto* listChildNode = enclosingListChild(position.deepEquivalent().deprecatedNode()); + if (!listChildNode || !isStartOfParagraph(position) || !isEndOfParagraph(position)) + return nullptr; VisiblePosition firstInListChild(firstPositionInOrBeforeNode(listChildNode)); VisiblePosition lastInListChild(lastPositionInOrAfterNode(listChildNode)); - if (firstInListChild != visiblePos || lastInListChild != visiblePos) - return 0; - + if (firstInListChild != position || lastInListChild != position) + return nullptr; + if (embeddedSublist(listChildNode) || appendedSublist(listChildNode)) - return 0; - + return nullptr; + return listChildNode; } HTMLElement* outermostEnclosingList(Node* node, Node* rootList) { - HTMLElement* list = enclosingList(node); + auto* list = enclosingList(node); if (!list) - return 0; + return nullptr; - while (HTMLElement* nextList = enclosingList(list)) { + while (auto* nextList = enclosingList(list)) { if (nextList == rootList) break; list = nextList; @@ -739,23 +764,17 @@ HTMLElement* outermostEnclosingList(Node* node, Node* rootList) bool canMergeLists(Element* firstList, Element* secondList) { - if (!firstList || !secondList || !firstList->isHTMLElement() || !secondList->isHTMLElement()) + if (!is<HTMLElement>(firstList) || !is<HTMLElement>(secondList)) return false; - return firstList->hasTagName(secondList->tagQName()) // make sure the list types match (ol vs. ul) - && firstList->hasEditableStyle() && secondList->hasEditableStyle() // both lists are editable - && firstList->rootEditableElement() == secondList->rootEditableElement() // don't cross editing boundaries - && isVisiblyAdjacent(positionInParentAfterNode(firstList), positionInParentBeforeNode(secondList)); - // Make sure there is no visible content between this li and the previous list -} + auto& first = downcast<HTMLElement>(*firstList); + auto& second = downcast<HTMLElement>(*secondList); -Node* highestAncestor(Node* node) -{ - ASSERT(node); - Node* parent = node; - while ((node = node->parentNode())) - parent = node; - return parent; + return first.localName() == second.localName() // make sure the list types match (ol vs. ul) + && first.hasEditableStyle() && second.hasEditableStyle() // both lists are editable + && first.rootEditableElement() == second.rootEditableElement() // don't cross editing boundaries + // Make sure there is no visible content between this li and the previous list. + && isVisiblyAdjacent(positionInParentAfterNode(&first), positionInParentBeforeNode(&second)); } static Node* previousNodeConsideringAtomicNodes(const Node* node) @@ -768,7 +787,7 @@ static Node* previousNodeConsideringAtomicNodes(const Node* node) } if (node->parentNode()) return node->parentNode(); - return 0; + return nullptr; } static Node* nextNodeConsideringAtomicNodes(const Node* node) @@ -782,48 +801,42 @@ static Node* nextNodeConsideringAtomicNodes(const Node* node) n = n->parentNode(); if (n) return n->nextSibling(); - return 0; + return nullptr; } Node* previousLeafNode(const Node* node) { - Node* n = previousNodeConsideringAtomicNodes(node); - while (n) { - if (isAtomicNode(n)) - return n; - n = previousNodeConsideringAtomicNodes(n); + while ((node = previousNodeConsideringAtomicNodes(node))) { + if (isAtomicNode(node)) + return const_cast<Node*>(node); } - return 0; + return nullptr; } Node* nextLeafNode(const Node* node) { - Node* n = nextNodeConsideringAtomicNodes(node); - while (n) { - if (isAtomicNode(n)) - return n; - n = nextNodeConsideringAtomicNodes(n); + while ((node = nextNodeConsideringAtomicNodes(node))) { + if (isAtomicNode(node)) + return const_cast<Node*>(node); } - return 0; + return nullptr; } -// FIXME: do not require renderer, so that this can be used within fragments, or rename to isRenderedTable() -bool isTableElement(Node* n) +// FIXME: Do not require renderer, so that this can be used within fragments. +bool isRenderedTable(const Node* node) { - if (!n || !n->isElementNode()) + if (!is<Element>(node)) return false; - - RenderObject* renderer = n->renderer(); - return (renderer && (renderer->style().display() == TABLE || renderer->style().display() == INLINE_TABLE)); + auto* renderer = downcast<Element>(*node).renderer(); + return renderer && renderer->isTable(); } bool isTableCell(const Node* node) { - RenderObject* r = node->renderer(); - if (!r) + auto* renderer = node->renderer(); + if (!renderer) return node->hasTagName(tdTag) || node->hasTagName(thTag); - - return r->isTableCell(); + return renderer->isTableCell(); } bool isEmptyTableCell(const Node* node) @@ -841,17 +854,17 @@ bool isEmptyTableCell(const Node* node) // Make sure the rendered node is a table cell or <br>. // If it's a <br>, then the parent node has to be a table cell. - RenderObject* renderer = node->renderer(); + auto* renderer = node->renderer(); if (renderer->isBR()) { renderer = renderer->parent(); if (!renderer) return false; } - if (!renderer->isTableCell()) + if (!is<RenderTableCell>(*renderer)) return false; // Check that the table cell contains no child renderers except for perhaps a single <br>. - RenderObject* childRenderer = toRenderElement(renderer)->firstChild(); + auto* childRenderer = downcast<RenderTableCell>(*renderer).firstChild(); if (!childRenderer) return true; if (!childRenderer->isBR()) @@ -859,206 +872,159 @@ bool isEmptyTableCell(const Node* node) return !childRenderer->nextSibling(); } -PassRefPtr<HTMLElement> createDefaultParagraphElement(Document& document) +Ref<HTMLElement> createDefaultParagraphElement(Document& document) { switch (document.frame()->editor().defaultParagraphSeparator()) { case EditorParagraphSeparatorIsDiv: return HTMLDivElement::create(document); case EditorParagraphSeparatorIsP: - return HTMLParagraphElement::create(document); + break; } - - ASSERT_NOT_REACHED(); - return 0; + return HTMLParagraphElement::create(document); } -PassRefPtr<HTMLElement> createBreakElement(Document& document) -{ - return HTMLBRElement::create(document); -} - -PassRefPtr<HTMLElement> createOrderedListElement(Document& document) -{ - return HTMLOListElement::create(document); -} - -PassRefPtr<HTMLElement> createUnorderedListElement(Document& document) -{ - return HTMLUListElement::create(document); -} - -PassRefPtr<HTMLElement> createListItemElement(Document& document) -{ - return HTMLLIElement::create(document); -} - -PassRefPtr<HTMLElement> createHTMLElement(Document& document, const QualifiedName& name) +Ref<HTMLElement> createHTMLElement(Document& document, const QualifiedName& name) { return HTMLElementFactory::createElement(name, document); } -PassRefPtr<HTMLElement> createHTMLElement(Document& document, const AtomicString& tagName) +Ref<HTMLElement> createHTMLElement(Document& document, const AtomicString& tagName) { return createHTMLElement(document, QualifiedName(nullAtom, tagName, xhtmlNamespaceURI)); } -bool isTabSpanNode(const Node *node) +bool isTabSpanNode(const Node* node) { - return node && node->hasTagName(spanTag) && node->isElementNode() && static_cast<const Element *>(node)->getAttribute(classAttr) == AppleTabSpanClass; + return is<HTMLSpanElement>(node) && downcast<HTMLSpanElement>(*node).attributeWithoutSynchronization(classAttr) == AppleTabSpanClass; } -bool isTabSpanTextNode(const Node *node) +bool isTabSpanTextNode(const Node* node) { - return node && node->isTextNode() && node->parentNode() && isTabSpanNode(node->parentNode()); + return is<Text>(node) && isTabSpanNode(node->parentNode()); } -Node* tabSpanNode(const Node *node) -{ - return isTabSpanTextNode(node) ? node->parentNode() : 0; -} - -Position positionOutsideTabSpan(const Position& pos) +HTMLSpanElement* tabSpanNode(const Node* node) { - Node* node = pos.containerNode(); - if (isTabSpanTextNode(node)) - node = tabSpanNode(node); - else if (!isTabSpanNode(node)) - return pos; - - if (node && VisiblePosition(pos) == lastPositionInNode(node)) - return positionInParentAfterNode(node); - - return positionInParentBeforeNode(node); + return isTabSpanTextNode(node) ? downcast<HTMLSpanElement>(node->parentNode()) : nullptr; } -PassRefPtr<Element> createTabSpanElement(Document& document, PassRefPtr<Node> prpTabTextNode) +static Ref<Element> createTabSpanElement(Document& document, Text& tabTextNode) { - RefPtr<Node> tabTextNode = prpTabTextNode; + auto spanElement = HTMLSpanElement::create(document); - // Make the span to hold the tab. - RefPtr<Element> spanElement = document.createElement(spanTag, false); - spanElement->setAttribute(classAttr, AppleTabSpanClass); + spanElement->setAttributeWithoutSynchronization(classAttr, AppleTabSpanClass); spanElement->setAttribute(styleAttr, "white-space:pre"); - // Add tab text to that span. - if (!tabTextNode) - tabTextNode = document.createEditingTextNode("\t"); + spanElement->appendChild(tabTextNode); - spanElement->appendChild(tabTextNode.release(), ASSERT_NO_EXCEPTION); - - return spanElement.release(); + return WTFMove(spanElement); } -PassRefPtr<Element> createTabSpanElement(Document& document, const String& tabText) +Ref<Element> createTabSpanElement(Document& document, const String& tabText) { return createTabSpanElement(document, document.createTextNode(tabText)); } -PassRefPtr<Element> createTabSpanElement(Document& document) +Ref<Element> createTabSpanElement(Document& document) { - return createTabSpanElement(document, PassRefPtr<Node>()); + return createTabSpanElement(document, document.createEditingTextNode(ASCIILiteral("\t"))); } -bool isNodeRendered(const Node* node) +bool isNodeRendered(const Node& node) { - if (!node) - return false; - - RenderObject* renderer = node->renderer(); - if (!renderer) - return false; - - return renderer->style().visibility() == VISIBLE; + auto* renderer = node.renderer(); + return renderer && renderer->style().visibility() == VISIBLE; } -unsigned numEnclosingMailBlockquotes(const Position& p) +unsigned numEnclosingMailBlockquotes(const Position& position) { - unsigned num = 0; - for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) - if (isMailBlockquote(n)) - num++; - - return num; + unsigned count = 0; + for (Node* node = position.deprecatedNode(); node; node = node->parentNode()) { + if (isMailBlockquote(node)) + ++count; + } + return count; } -void updatePositionForNodeRemoval(Position& position, Node* node) +void updatePositionForNodeRemoval(Position& position, Node& node) { if (position.isNull()) return; switch (position.anchorType()) { case Position::PositionIsBeforeChildren: - if (position.containerNode() == node) - position = positionInParentBeforeNode(node); + if (node.containsIncludingShadowDOM(position.containerNode())) + position = positionInParentBeforeNode(&node); break; case Position::PositionIsAfterChildren: - if (position.containerNode() == node) - position = positionInParentAfterNode(node); + if (node.containsIncludingShadowDOM(position.containerNode())) + position = positionInParentBeforeNode(&node); break; case Position::PositionIsOffsetInAnchor: - if (position.containerNode() == node->parentNode() && static_cast<unsigned>(position.offsetInContainerNode()) > node->nodeIndex()) + if (position.containerNode() == node.parentNode() && static_cast<unsigned>(position.offsetInContainerNode()) > node.computeNodeIndex()) position.moveToOffset(position.offsetInContainerNode() - 1); - else if (node->containsIncludingShadowDOM(position.containerNode())) - position = positionInParentBeforeNode(node); + else if (node.containsIncludingShadowDOM(position.containerNode())) + position = positionInParentBeforeNode(&node); break; case Position::PositionIsAfterAnchor: - if (node->containsIncludingShadowDOM(position.anchorNode())) - position = positionInParentAfterNode(node); + if (node.containsIncludingShadowDOM(position.anchorNode())) + position = positionInParentAfterNode(&node); break; case Position::PositionIsBeforeAnchor: - if (node->containsIncludingShadowDOM(position.anchorNode())) - position = positionInParentBeforeNode(node); + if (node.containsIncludingShadowDOM(position.anchorNode())) + position = positionInParentBeforeNode(&node); break; } } -bool isMailBlockquote(const Node *node) +bool isMailBlockquote(const Node* node) { - if (!node || !node->hasTagName(blockquoteTag)) + ASSERT(node); + if (!node->hasTagName(blockquoteTag)) return false; - - return static_cast<const Element *>(node)->getAttribute("type") == "cite"; + return downcast<HTMLElement>(*node).attributeWithoutSynchronization(typeAttr) == "cite"; } -int caretMinOffset(const Node* n) +int caretMinOffset(const Node& node) { - RenderObject* r = n->renderer(); - ASSERT(!n->isCharacterDataNode() || !r || r->isText()); // FIXME: This was a runtime check that seemingly couldn't fail; changed it to an assertion for now. - return r ? r->caretMinOffset() : 0; + auto* renderer = node.renderer(); + ASSERT(!node.isCharacterDataNode() || !renderer || renderer->isText()); + return renderer ? renderer->caretMinOffset() : 0; } // If a node can contain candidates for VisiblePositions, return the offset of the last candidate, otherwise // return the number of children for container nodes and the length for unrendered text nodes. -int caretMaxOffset(const Node* n) +int caretMaxOffset(const Node& node) { // For rendered text nodes, return the last position that a caret could occupy. - if (n->isTextNode() && n->renderer()) - return n->renderer()->caretMaxOffset(); - // For containers return the number of children. For others do the same as above. - return lastOffsetForEditing(n); + if (is<Text>(node)) { + if (auto* renderer = downcast<Text>(node).renderer()) + return renderer->caretMaxOffset(); + } + return lastOffsetForEditing(node); } -bool lineBreakExistsAtVisiblePosition(const VisiblePosition& visiblePosition) +bool lineBreakExistsAtVisiblePosition(const VisiblePosition& position) { - return lineBreakExistsAtPosition(visiblePosition.deepEquivalent().downstream()); + return lineBreakExistsAtPosition(position.deepEquivalent().downstream()); } bool lineBreakExistsAtPosition(const Position& position) { if (position.isNull()) return false; - + if (position.anchorNode()->hasTagName(brTag) && position.atFirstEditingPositionForNode()) return true; - + if (!position.anchorNode()->renderer()) return false; - - if (!position.anchorNode()->isTextNode() || !position.anchorNode()->renderer()->style().preserveNewline()) + + if (!is<Text>(*position.anchorNode()) || !position.anchorNode()->renderer()->style().preserveNewline()) return false; - Text* textNode = toText(position.anchorNode()); + Text& textNode = downcast<Text>(*position.anchorNode()); unsigned offset = position.offsetInContainerNode(); - return offset < textNode->length() && textNode->data()[offset] == '\n'; + return offset < textNode.length() && textNode.data()[offset] == '\n'; } // Modifies selections that have an end point at the edge of a table @@ -1074,17 +1040,19 @@ VisibleSelection selectionForParagraphIteration(const VisibleSelection& original // if the start of the selection is inside that table, then the last paragraph // that we'll want modify is the last one inside the table, not the table itself // (a table is itself a paragraph). - if (Node* table = isFirstPositionAfterTable(endOfSelection)) - if (startOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(table)) + if (auto* table = isFirstPositionAfterTable(endOfSelection)) { + if (startOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(*table)) newSelection = VisibleSelection(startOfSelection, endOfSelection.previous(CannotCrossEditingBoundary)); + } // If the start of the selection to modify is just before a table, // and if the end of the selection is inside that table, then the first paragraph // we'll want to modify is the first one inside the table, not the paragraph // containing the table itself. - if (Node* table = isLastPositionBeforeTable(startOfSelection)) - if (endOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(table)) + if (auto* table = isLastPositionBeforeTable(startOfSelection)) { + if (endOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(*table)) newSelection = VisibleSelection(startOfSelection.next(CannotCrossEditingBoundary), endOfSelection); + } return newSelection; } @@ -1100,74 +1068,74 @@ int indexForVisiblePosition(const VisiblePosition& visiblePosition, RefPtr<Conta if (visiblePosition.isNull()) return 0; - Position p(visiblePosition.deepEquivalent()); - Document& document = p.anchorNode()->document(); - ShadowRoot* shadowRoot = p.anchorNode()->containingShadowRoot(); - - if (shadowRoot) - scope = shadowRoot; - else - scope = document.documentElement(); + auto position = visiblePosition.deepEquivalent(); + auto& document = *position.document(); + + auto* editableRoot = highestEditableRoot(position, AXObjectCache::accessibilityEnabled() ? HasEditableAXRole : ContentIsEditable); + if (editableRoot && !document.inDesignMode()) + scope = editableRoot; + else { + if (position.containerNode()->isInShadowTree()) + scope = position.containerNode()->containingShadowRoot(); + else + scope = &document; + } - RefPtr<Range> range = Range::create(document, firstPositionInNode(scope.get()), p.parentAnchoredEquivalent()); - return TextIterator::rangeLength(range.get(), true); + auto range = Range::create(document, firstPositionInNode(scope.get()), position.parentAnchoredEquivalent()); + return TextIterator::rangeLength(range.ptr(), true); } -// FIXME: Merge these two functions. -int indexForVisiblePosition(Node* node, const VisiblePosition& visiblePosition, bool forSelectionPreservation) +// FIXME: Merge this function with the one above. +int indexForVisiblePosition(Node& node, const VisiblePosition& visiblePosition, bool forSelectionPreservation) { - ASSERT(node); - RefPtr<Range> range = Range::create(node->document(), firstPositionInNode(node), visiblePosition.deepEquivalent().parentAnchoredEquivalent()); - return TextIterator::rangeLength(range.get(), forSelectionPreservation); + auto range = Range::create(node.document(), firstPositionInNode(&node), visiblePosition.deepEquivalent().parentAnchoredEquivalent()); + return TextIterator::rangeLength(range.ptr(), forSelectionPreservation); } VisiblePosition visiblePositionForIndex(int index, ContainerNode* scope) { - RefPtr<Range> range = TextIterator::rangeFromLocationAndLength(scope, index, 0, true); + auto range = TextIterator::rangeFromLocationAndLength(scope, index, 0, true); // Check for an invalid index. Certain editing operations invalidate indices because // of problems with TextIteratorEmitsCharactersBetweenAllVisiblePositions. if (!range) - return VisiblePosition(); - return VisiblePosition(range->startPosition()); + return { }; + return { range->startPosition() }; } -VisiblePosition visiblePositionForIndexUsingCharacterIterator(Node* node, int index) +VisiblePosition visiblePositionForIndexUsingCharacterIterator(Node& node, int index) { - ASSERT(node); if (index <= 0) - return VisiblePosition(firstPositionInOrBeforeNode(node), DOWNSTREAM); + return { firstPositionInOrBeforeNode(&node), DOWNSTREAM }; - RefPtr<Range> range = Range::create(node->document()); - range->selectNodeContents(node, IGNORE_EXCEPTION); - CharacterIterator it(range.get()); + RefPtr<Range> range = Range::create(node.document()); + range->selectNodeContents(node); + CharacterIterator it(*range); it.advance(index - 1); - - return VisiblePosition(Position(it.range()->endContainer(), it.range()->endOffset(), Position::PositionIsOffsetInAnchor), UPSTREAM); + return { it.atEnd() ? range->endPosition() : it.range()->endPosition(), UPSTREAM }; } // Determines whether two positions are visibly next to each other (first then second) // while ignoring whitespaces and unrendered nodes -bool isVisiblyAdjacent(const Position& first, const Position& second) +static bool isVisiblyAdjacent(const Position& first, const Position& second) { return VisiblePosition(first) == VisiblePosition(second.upstream()); } // Determines whether a node is inside a range or visibly starts and ends at the boundaries of the range. // Call this function to determine whether a node is visibly fit inside selectedRange -bool isNodeVisiblyContainedWithin(Node* node, const Range* selectedRange) +bool isNodeVisiblyContainedWithin(Node& node, const Range& range) { - ASSERT(node); - ASSERT(selectedRange); - // If the node is inside the range, then it surely is contained within - if (selectedRange->compareNode(node, IGNORE_EXCEPTION) == Range::NODE_INSIDE) + // If the node is inside the range, then it surely is contained within. + auto comparisonResult = range.compareNode(node); + if (!comparisonResult.hasException() && comparisonResult.releaseReturnValue() == Range::NODE_INSIDE) return true; - bool startIsVisuallySame = visiblePositionBeforeNode(node) == selectedRange->startPosition(); - if (startIsVisuallySame && comparePositions(positionInParentAfterNode(node), selectedRange->endPosition()) < 0) + bool startIsVisuallySame = visiblePositionBeforeNode(node) == range.startPosition(); + if (startIsVisuallySame && comparePositions(positionInParentAfterNode(&node), range.endPosition()) < 0) return true; - bool endIsVisuallySame = visiblePositionAfterNode(node) == selectedRange->endPosition(); - if (endIsVisuallySame && comparePositions(selectedRange->startPosition(), positionInParentBeforeNode(node)) < 0) + bool endIsVisuallySame = visiblePositionAfterNode(node) == range.endPosition(); + if (endIsVisuallySame && comparePositions(range.startPosition(), positionInParentBeforeNode(&node)) < 0) return true; return startIsVisuallySame && endIsVisuallySame; @@ -1181,36 +1149,28 @@ bool isRenderedAsNonInlineTableImageOrHR(const Node* node) return renderer && ((renderer->isTable() && !renderer->isInline()) || (renderer->isImage() && !renderer->isInline()) || renderer->isHR()); } -bool areIdenticalElements(const Node* first, const Node* second) +bool areIdenticalElements(const Node& first, const Node& second) { - if (!first->isElementNode() || !second->isElementNode()) - return false; - - const Element* firstElement = toElement(first); - const Element* secondElement = toElement(second); - if (!firstElement->hasTagName(secondElement->tagQName())) + if (!is<Element>(first) || !is<Element>(second)) return false; - - return firstElement->hasEquivalentAttributes(secondElement); + auto& firstElement = downcast<Element>(first); + auto& secondElement = downcast<Element>(second); + return firstElement.hasTagName(secondElement.tagQName()) && firstElement.hasEquivalentAttributes(&secondElement); } bool isNonTableCellHTMLBlockElement(const Node* node) { - if (!node->isElementNode()) - return false; - - const Element* element = toElement(node); - return element->hasTagName(listingTag) - || element->hasTagName(olTag) - || element->hasTagName(preTag) - || isHTMLTableElement(element) - || element->hasTagName(ulTag) - || element->hasTagName(xmpTag) - || element->hasTagName(h1Tag) - || element->hasTagName(h2Tag) - || element->hasTagName(h3Tag) - || element->hasTagName(h4Tag) - || element->hasTagName(h5Tag); + return node->hasTagName(listingTag) + || node->hasTagName(olTag) + || node->hasTagName(preTag) + || is<HTMLTableElement>(*node) + || node->hasTagName(ulTag) + || node->hasTagName(xmpTag) + || node->hasTagName(h1Tag) + || node->hasTagName(h2Tag) + || node->hasTagName(h3Tag) + || node->hasTagName(h4Tag) + || node->hasTagName(h5Tag); } Position adjustedSelectionStartForStyleComputation(const VisibleSelection& selection) @@ -1220,9 +1180,9 @@ Position adjustedSelectionStartForStyleComputation(const VisibleSelection& selec // It is important to skip certain irrelevant content at the start of the selection, so we do not wind up // with a spurious "mixed" style. - VisiblePosition visiblePosition = selection.start(); + auto visiblePosition = selection.visibleStart(); if (visiblePosition.isNull()) - return Position(); + return { }; // if the selection is a caret, just return the position, since the style // behind us is relevant @@ -1239,25 +1199,89 @@ Position adjustedSelectionStartForStyleComputation(const VisibleSelection& selec } // FIXME: Should this be deprecated like deprecatedEnclosingBlockFlowElement is? -bool isBlockFlowElement(const Node* node) +bool isBlockFlowElement(const Node& node) { - if (!node->isElementNode()) + if (!node.isElementNode()) return false; - RenderObject* renderer = node->renderer(); + auto* renderer = downcast<Element>(node).renderer(); return renderer && renderer->isRenderBlockFlow(); } Element* deprecatedEnclosingBlockFlowElement(Node* node) { if (!node) - return 0; - if (isBlockFlowElement(node)) - return toElement(node); + return nullptr; + if (isBlockFlowElement(*node)) + return downcast<Element>(node); while ((node = node->parentNode())) { - if (isBlockFlowElement(node) || node->hasTagName(bodyTag)) - return toElement(node); + if (isBlockFlowElement(*node) || is<HTMLBodyElement>(*node)) + return downcast<Element>(node); } - return 0; + return nullptr; +} + +static inline bool caretRendersInsideNode(Node& node) +{ + return !isRenderedTable(&node) && !editingIgnoresContent(node); +} + +RenderBlock* rendererForCaretPainting(Node* node) +{ + if (!node) + return nullptr; + + auto* renderer = node->renderer(); + if (!renderer) + return nullptr; + + // If caretNode is a block and caret is inside it, then caret should be painted by that block. + bool paintedByBlock = is<RenderBlockFlow>(*renderer) && caretRendersInsideNode(*node); + return paintedByBlock ? downcast<RenderBlock>(renderer) : renderer->containingBlock(); +} + +LayoutRect localCaretRectInRendererForCaretPainting(const VisiblePosition& caretPosition, RenderBlock*& caretPainter) +{ + if (caretPosition.isNull()) + return LayoutRect(); + + ASSERT(caretPosition.deepEquivalent().deprecatedNode()->renderer()); + + // First compute a rect local to the renderer at the selection start. + RenderObject* renderer; + LayoutRect localRect = caretPosition.localCaretRect(renderer); + + return localCaretRectInRendererForRect(localRect, caretPosition.deepEquivalent().deprecatedNode(), renderer, caretPainter); +} + +LayoutRect localCaretRectInRendererForRect(LayoutRect& localRect, Node* node, RenderObject* renderer, RenderBlock*& caretPainter) +{ + // Get the renderer that will be responsible for painting the caret + // (which is either the renderer we just found, or one of its containers). + caretPainter = rendererForCaretPainting(node); + + // Compute an offset between the renderer and the caretPainter. + while (renderer != caretPainter) { + RenderElement* containerObject = renderer->container(); + if (!containerObject) + return LayoutRect(); + localRect.move(renderer->offsetFromContainer(*containerObject, localRect.location())); + renderer = containerObject; + } + + return localRect; +} + +IntRect absoluteBoundsForLocalCaretRect(RenderBlock* rendererForCaretPainting, const LayoutRect& rect, bool* insideFixed) +{ + if (insideFixed) + *insideFixed = false; + + if (!rendererForCaretPainting || rect.isEmpty()) + return IntRect(); + + LayoutRect localRect(rect); + rendererForCaretPainting->flipForWritingMode(localRect); + return rendererForCaretPainting->localToAbsoluteQuad(FloatRect(localRect), UseTransforms, insideFixed).enclosingBoundingBox(); } } // namespace WebCore diff --git a/Source/WebCore/editing/htmlediting.h b/Source/WebCore/editing/htmlediting.h index 94e74e352..eae48c0bb 100644 --- a/Source/WebCore/editing/htmlediting.h +++ b/Source/WebCore/editing/htmlediting.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2004, 2006, 2008, 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,239 +23,221 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef htmlediting_h -#define htmlediting_h +#pragma once -#include "EditingBoundary.h" #include "Position.h" -#include "TextDirection.h" #include <wtf/Forward.h> #include <wtf/unicode/CharacterNames.h> namespace WebCore { class Document; -class Element; class HTMLElement; +class HTMLSpanElement; class HTMLTextFormControlElement; -class Node; -class Position; -class Range; +class RenderBlock; class VisiblePosition; class VisibleSelection; - -// This file contains a set of helper functions used by the editing commands - // ------------------------------------------------------------------------- // Node // ------------------------------------------------------------------------- -// Functions returning Node +ContainerNode* highestEditableRoot(const Position&, EditableType = ContentIsEditable); -Node* highestAncestor(Node*); -Node* highestEditableRoot(const Position&, EditableType = ContentIsEditable); - -Node* highestEnclosingNodeOfType(const Position&, bool (*nodeIsOfType)(const Node*), - EditingBoundaryCrossingRule = CannotCrossEditingBoundary, Node* stayWithin = 0); +Node* highestEnclosingNodeOfType(const Position&, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule = CannotCrossEditingBoundary, Node* stayWithin = nullptr); Node* highestNodeToRemoveInPruning(Node*); -Node* lowestEditableAncestor(Node*); +Element* lowestEditableAncestor(Node*); Element* deprecatedEnclosingBlockFlowElement(Node*); // Use enclosingBlock instead. Element* enclosingBlock(Node*, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); -Node* enclosingTableCell(const Position&); +Element* enclosingTableCell(const Position&); Node* enclosingEmptyListItem(const VisiblePosition&); Element* enclosingAnchorElement(const Position&); -Node* enclosingNodeWithTag(const Position&, const QualifiedName&); +Element* enclosingElementWithTag(const Position&, const QualifiedName&); Node* enclosingNodeOfType(const Position&, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule = CannotCrossEditingBoundary); - -Node* tabSpanNode(const Node*); -Node* isLastPositionBeforeTable(const VisiblePosition&); -Node* isFirstPositionAfterTable(const VisiblePosition&); +HTMLSpanElement* tabSpanNode(const Node*); +Element* isLastPositionBeforeTable(const VisiblePosition&); // FIXME: Strange to name this isXXX, but return an element. +Element* isFirstPositionAfterTable(const VisiblePosition&); // FIXME: Strange to name this isXXX, but return an element. // These two deliver leaf nodes as if the whole DOM tree were a linear chain of its leaf nodes. Node* nextLeafNode(const Node*); Node* previousLeafNode(const Node*); -// offset functions on Node +WEBCORE_EXPORT int lastOffsetForEditing(const Node&); +int caretMinOffset(const Node&); +int caretMaxOffset(const Node&); -int lastOffsetForEditing(const Node*); -int caretMinOffset(const Node*); -int caretMaxOffset(const Node*); +bool hasEditableStyle(const Node&, EditableType); +bool isEditableNode(const Node&); -// boolean functions on Node - -// FIXME: editingIgnoresContent, canHaveChildrenForEditing, and isAtomicNode -// should be renamed to reflect its usage. +// FIXME: editingIgnoresContent, canHaveChildrenForEditing, and isAtomicNode should be named to clarify how they differ. // Returns true for nodes that either have no content, or have content that is ignored (skipped over) while editing. // There are no VisiblePositions inside these nodes. -inline bool editingIgnoresContent(const Node* node) -{ - return !node->canContainRangeEndPoint(); -} - -inline bool canHaveChildrenForEditing(const Node* node) -{ - return !node->isTextNode() && node->canContainRangeEndPoint(); -} +bool editingIgnoresContent(const Node&); +bool canHaveChildrenForEditing(const Node&); bool isAtomicNode(const Node*); + bool isBlock(const Node*); -bool isBlockFlowElement(const Node*); +bool isBlockFlowElement(const Node&); bool isInline(const Node*); -bool isSpecialElement(const Node*); bool isTabSpanNode(const Node*); bool isTabSpanTextNode(const Node*); bool isMailBlockquote(const Node*); -bool isTableElement(Node*); +bool isRenderedTable(const Node*); bool isTableCell(const Node*); bool isEmptyTableCell(const Node*); bool isTableStructureNode(const Node*); -bool isListElement(Node*); +bool isListHTMLElement(Node*); bool isListItem(const Node*); -bool isNodeRendered(const Node*); -bool isNodeVisiblyContainedWithin(Node*, const Range*); +bool isNodeRendered(const Node&); bool isRenderedAsNonInlineTableImageOrHR(const Node*); -bool areIdenticalElements(const Node*, const Node*); bool isNonTableCellHTMLBlockElement(const Node*); -TextDirection directionOfEnclosingBlock(const Position&); +bool isNodeVisiblyContainedWithin(Node&, const Range&); + +bool areIdenticalElements(const Node&, const Node&); + +bool positionBeforeOrAfterNodeIsCandidate(Node&); // ------------------------------------------------------------------------- // Position // ------------------------------------------------------------------------- - -// Functions returning Position - + Position nextCandidate(const Position&); Position previousCandidate(const Position&); - + Position nextVisuallyDistinctCandidate(const Position&); Position previousVisuallyDistinctCandidate(const Position&); -Position positionOutsideTabSpan(const Position&); -Position positionBeforeContainingSpecialElement(const Position&, Node** containingSpecialElement = 0); -Position positionAfterContainingSpecialElement(const Position&, Node** containingSpecialElement = 0); -Position positionOutsideContainingSpecialElement(const Position&, Node** containingSpecialElement = 0); +Position positionBeforeContainingSpecialElement(const Position&, HTMLElement** containingSpecialElement = nullptr); +Position positionAfterContainingSpecialElement(const Position&, HTMLElement** containingSpecialElement = nullptr); -inline Position firstPositionInOrBeforeNode(Node* node) -{ - if (!node) - return Position(); - return editingIgnoresContent(node) ? positionBeforeNode(node) : firstPositionInNode(node); -} +Position firstPositionInOrBeforeNode(Node*); +Position lastPositionInOrAfterNode(Node*); -inline Position lastPositionInOrAfterNode(Node* node) -{ - if (!node) - return Position(); - return editingIgnoresContent(node) ? positionAfterNode(node) : lastPositionInNode(node); -} +Position firstEditablePositionAfterPositionInRoot(const Position&, ContainerNode* root); +Position lastEditablePositionBeforePositionInRoot(const Position&, ContainerNode* root); -// comparision functions on Position - int comparePositions(const Position&, const Position&); -// boolean functions on Position - -enum EUpdateStyle { UpdateStyle, DoNotUpdateStyle }; -bool isEditablePosition(const Position&, EditableType = ContentIsEditable, EUpdateStyle = UpdateStyle); -bool isRichlyEditablePosition(const Position&, EditableType = ContentIsEditable); -bool isFirstVisiblePositionInSpecialElement(const Position&); -bool isLastVisiblePositionInSpecialElement(const Position&); +WEBCORE_EXPORT bool isEditablePosition(const Position&, EditableType = ContentIsEditable); +bool isRichlyEditablePosition(const Position&); bool lineBreakExistsAtPosition(const Position&); -bool isVisiblyAdjacent(const Position& first, const Position& second); bool isAtUnsplittableElement(const Position&); -// miscellaneous functions on Position - unsigned numEnclosingMailBlockquotes(const Position&); -void updatePositionForNodeRemoval(Position&, Node*); +void updatePositionForNodeRemoval(Position&, Node&); + +WEBCORE_EXPORT TextDirection directionOfEnclosingBlock(const Position&); // ------------------------------------------------------------------------- // VisiblePosition // ------------------------------------------------------------------------- - -// Functions returning VisiblePosition - -VisiblePosition firstEditablePositionAfterPositionInRoot(const Position&, Node*); -VisiblePosition lastEditablePositionBeforePositionInRoot(const Position&, Node*); -VisiblePosition visiblePositionBeforeNode(Node*); -VisiblePosition visiblePositionAfterNode(Node*); + +VisiblePosition visiblePositionBeforeNode(Node&); +VisiblePosition visiblePositionAfterNode(Node&); bool lineBreakExistsAtVisiblePosition(const VisiblePosition&); - + int comparePositions(const VisiblePosition&, const VisiblePosition&); -int indexForVisiblePosition(const VisiblePosition&, RefPtr<ContainerNode>& scope); -int indexForVisiblePosition(Node*, const VisiblePosition&, bool forSelectionPreservation); -VisiblePosition visiblePositionForIndex(int index, ContainerNode* scope); -VisiblePosition visiblePositionForIndexUsingCharacterIterator(Node*, int index); // FIXME: Why do we need this version? +WEBCORE_EXPORT int indexForVisiblePosition(const VisiblePosition&, RefPtr<ContainerNode>& scope); +int indexForVisiblePosition(Node&, const VisiblePosition&, bool forSelectionPreservation); +WEBCORE_EXPORT VisiblePosition visiblePositionForIndex(int index, ContainerNode* scope); +VisiblePosition visiblePositionForIndexUsingCharacterIterator(Node&, int index); // FIXME: Why do we need this version? // ------------------------------------------------------------------------- // HTMLElement // ------------------------------------------------------------------------- - -// Functions returning HTMLElement - -PassRefPtr<HTMLElement> createDefaultParagraphElement(Document&); -PassRefPtr<HTMLElement> createBreakElement(Document&); -PassRefPtr<HTMLElement> createOrderedListElement(Document&); -PassRefPtr<HTMLElement> createUnorderedListElement(Document&); -PassRefPtr<HTMLElement> createListItemElement(Document&); -PassRefPtr<HTMLElement> createHTMLElement(Document&, const QualifiedName&); -PassRefPtr<HTMLElement> createHTMLElement(Document&, const AtomicString&); - -HTMLElement* enclosingList(Node*); -HTMLElement* outermostEnclosingList(Node*, Node* rootList = 0); + +WEBCORE_EXPORT Ref<HTMLElement> createDefaultParagraphElement(Document&); +Ref<HTMLElement> createHTMLElement(Document&, const QualifiedName&); +Ref<HTMLElement> createHTMLElement(Document&, const AtomicString&); + +WEBCORE_EXPORT HTMLElement* enclosingList(Node*); +HTMLElement* outermostEnclosingList(Node*, Node* rootList = nullptr); Node* enclosingListChild(Node*); // ------------------------------------------------------------------------- // Element // ------------------------------------------------------------------------- - -// Functions returning Element - -PassRefPtr<Element> createTabSpanElement(Document&); -PassRefPtr<Element> createTabSpanElement(Document&, PassRefPtr<Node> tabTextNode); -PassRefPtr<Element> createTabSpanElement(Document&, const String& tabText); -PassRefPtr<Element> createBlockPlaceholderElement(Document&); + +Ref<Element> createTabSpanElement(Document&); +Ref<Element> createTabSpanElement(Document&, const String& tabText); +Ref<Element> createBlockPlaceholderElement(Document&); Element* editableRootForPosition(const Position&, EditableType = ContentIsEditable); Element* unsplittableElementForPosition(const Position&); -// Boolean functions on Element - bool canMergeLists(Element* firstList, Element* secondList); - + // ------------------------------------------------------------------------- // VisibleSelection // ------------------------------------------------------------------------- -// Functions returning VisibleSelection VisibleSelection selectionForParagraphIteration(const VisibleSelection&); - Position adjustedSelectionStartForStyleComputation(const VisibleSelection&); - -// Miscellaneous functions on Text -inline bool isWhitespace(UChar c) +// ------------------------------------------------------------------------- + +// FIXME: This is only one of many definitions of whitespace. Possibly never the right one to use. +bool deprecatedIsEditingWhitespace(UChar); + +// FIXME: Can't answer this question correctly without being passed the white-space mode. +bool deprecatedIsCollapsibleWhitespace(UChar); + +bool isAmbiguousBoundaryCharacter(UChar); + +String stringWithRebalancedWhitespace(const String&, bool startIsStartOfParagraph, bool endIsEndOfParagraph); +const String& nonBreakingSpaceString(); + +// Miscellaneous functions for caret rendering. + +RenderBlock* rendererForCaretPainting(Node*); +LayoutRect localCaretRectInRendererForCaretPainting(const VisiblePosition&, RenderBlock*&); +LayoutRect localCaretRectInRendererForRect(LayoutRect&, Node*, RenderObject*, RenderBlock*&); +IntRect absoluteBoundsForLocalCaretRect(RenderBlock* rendererForCaretPainting, const LayoutRect&, bool* insideFixed = nullptr); + +// ------------------------------------------------------------------------- + +inline bool deprecatedIsEditingWhitespace(UChar c) { return c == noBreakSpace || c == ' ' || c == '\n' || c == '\t'; } -inline bool isAmbiguousBoundaryCharacter(UChar character) +// FIXME: Can't really answer this question correctly without knowing the white-space mode. +inline bool deprecatedIsCollapsibleWhitespace(UChar c) { - // These are characters that can behave as word boundaries, but can appear within words. - // If they are just typed, i.e. if they are immediately followed by a caret, we want to delay text checking until the next character has been typed. - // FIXME: this is required until 6853027 is fixed and text checking can do this for us. - return character == '\'' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim; + return c == ' ' || c == '\n'; } -String stringWithRebalancedWhitespace(const String&, bool startIsStartOfParagraph, bool endIsEndOfParagraph); -const String& nonBreakingSpaceString(); +bool isAmbiguousBoundaryCharacter(UChar); +inline bool editingIgnoresContent(const Node& node) +{ + return !node.canContainRangeEndPoint(); +} + +inline bool positionBeforeOrAfterNodeIsCandidate(Node& node) +{ + return isRenderedTable(&node) || editingIgnoresContent(node); } -#endif +inline Position firstPositionInOrBeforeNode(Node* node) +{ + if (!node) + return { }; + return editingIgnoresContent(*node) ? positionBeforeNode(node) : firstPositionInNode(node); +} + +inline Position lastPositionInOrAfterNode(Node* node) +{ + if (!node) + return { }; + return editingIgnoresContent(*node) ? positionAfterNode(node) : lastPositionInNode(node); +} + +} diff --git a/Source/WebCore/editing/markup.cpp b/Source/WebCore/editing/markup.cpp index 5b6173b15..db57589af 100644 --- a/Source/WebCore/editing/markup.cpp +++ b/Source/WebCore/editing/markup.cpp @@ -13,10 +13,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -29,7 +29,6 @@ #include "config.h" #include "markup.h" -#include "CDATASection.h" #include "CSSPrimitiveValue.h" #include "CSSPropertyNames.h" #include "CSSValue.h" @@ -40,30 +39,33 @@ #include "Editor.h" #include "ElementIterator.h" #include "ExceptionCode.h" -#include "ExceptionCodePlaceholder.h" +#include "File.h" #include "Frame.h" +#include "HTMLAttachmentElement.h" +#include "HTMLBRElement.h" #include "HTMLBodyElement.h" -#include "HTMLElement.h" +#include "HTMLDivElement.h" +#include "HTMLHeadElement.h" +#include "HTMLHtmlElement.h" #include "HTMLNames.h" #include "HTMLTableElement.h" #include "HTMLTextAreaElement.h" #include "HTMLTextFormControlElement.h" #include "URL.h" #include "MarkupAccumulator.h" +#include "NodeList.h" #include "Range.h" #include "RenderBlock.h" +#include "Settings.h" #include "StyleProperties.h" #include "TextIterator.h" +#include "TypedElementDescendantIterator.h" #include "VisibleSelection.h" #include "VisibleUnits.h" #include "htmlediting.h" #include <wtf/StdLibExtras.h> #include <wtf/text/StringBuilder.h> -#if ENABLE(DELETION_UI) -#include "DeleteButtonController.h" -#endif - namespace WebCore { using namespace HTMLNames; @@ -77,7 +79,7 @@ public: { } - AttributeChange(PassRefPtr<Element> element, const QualifiedName& name, const String& value) + AttributeChange(Element* element, const QualifiedName& name, const String& value) : m_element(element), m_name(name), m_value(value) { } @@ -103,26 +105,28 @@ static void completeURLs(DocumentFragment* fragment, const String& baseURL) if (!element.hasAttributes()) continue; for (const Attribute& attribute : element.attributesIterator()) { - if (element.isURLAttribute(attribute) && !attribute.value().isEmpty()) - changes.append(AttributeChange(&element, attribute.name(), URL(parsedBaseURL, attribute.value()).string())); + if (element.attributeContainsURL(attribute) && !attribute.value().isEmpty()) + changes.append(AttributeChange(&element, attribute.name(), element.completeURLsInAttributeValue(parsedBaseURL, attribute))); } } - size_t numChanges = changes.size(); - for (size_t i = 0; i < numChanges; ++i) - changes[i].apply(); + for (auto& change : changes) + change.apply(); } class StyledMarkupAccumulator final : public MarkupAccumulator { public: enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode }; - StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs, EAnnotateForInterchange, const Range*, Node* highestNodeToBeSerialized = 0); + StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs, EAnnotateForInterchange, const Range*, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized = nullptr); Node* serializeNodes(Node* startNode, Node* pastEnd); void wrapWithNode(Node&, bool convertBlocksToInlines = false, RangeFullySelectsNode = DoesFullySelectNode); void wrapWithStyleNode(StyleProperties*, Document&, bool isBlock = false); String takeResults(); + + bool needRelativeStyleWrapper() const { return m_needRelativeStyleWrapper; } + bool needClearingDiv() const { return m_needClearingDiv; } using MarkupAccumulator::appendString; @@ -134,9 +138,10 @@ private: String stringValueForRange(const Node&, const Range*); void appendElement(StringBuilder& out, const Element&, bool addDisplayInline, RangeFullySelectsNode); + void appendCustomAttributes(StringBuilder&, const Element&, Namespaces*) override; - virtual void appendText(StringBuilder& out, const Text&) override; - virtual void appendElement(StringBuilder& out, const Element& element, Namespaces*) override + void appendText(StringBuilder& out, const Text&) override; + void appendElement(StringBuilder& out, const Element& element, Namespaces*) override { appendElement(out, element, false, DoesFullySelectNode); } @@ -158,23 +163,28 @@ private: const EAnnotateForInterchange m_shouldAnnotate; Node* m_highestNodeToBeSerialized; RefPtr<EditingStyle> m_wrappingStyle; + bool m_needRelativeStyleWrapper; + bool m_needsPositionStyleConversion; + bool m_needClearingDiv; }; -inline StyledMarkupAccumulator::StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, EAnnotateForInterchange shouldAnnotate, - const Range* range, Node* highestNodeToBeSerialized) +inline StyledMarkupAccumulator::StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, EAnnotateForInterchange shouldAnnotate, const Range* range, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized) : MarkupAccumulator(nodes, shouldResolveURLs, range) , m_shouldAnnotate(shouldAnnotate) , m_highestNodeToBeSerialized(highestNodeToBeSerialized) + , m_needRelativeStyleWrapper(false) + , m_needsPositionStyleConversion(needsPositionStyleConversion) + , m_needClearingDiv(false) { } void StyledMarkupAccumulator::wrapWithNode(Node& node, bool convertBlocksToInlines, RangeFullySelectsNode rangeFullySelectsNode) { StringBuilder markup; - if (node.isElementNode()) - appendElement(markup, toElement(node), convertBlocksToInlines && isBlock(&node), rangeFullySelectsNode); + if (is<Element>(node)) + appendElement(markup, downcast<Element>(node), convertBlocksToInlines && isBlock(&node), rangeFullySelectsNode); else - appendStartMarkup(markup, node, 0); + appendStartMarkup(markup, node, nullptr); m_reversedPrecedingMarkup.append(markup.toString()); appendEndTag(node); if (m_nodes) @@ -203,8 +213,8 @@ void StyledMarkupAccumulator::appendStyleNodeOpenTag(StringBuilder& out, StylePr const String& StyledMarkupAccumulator::styleNodeCloseTag(bool isBlock) { - DEFINE_STATIC_LOCAL(const String, divClose, (ASCIILiteral("</div>"))); - DEFINE_STATIC_LOCAL(const String, styleSpanClose, (ASCIILiteral("</span>"))); + static NeverDestroyed<const String> divClose(ASCIILiteral("</div>")); + static NeverDestroyed<const String> styleSpanClose(ASCIILiteral("</span>")); return isBlock ? divClose : styleSpanClose; } @@ -224,7 +234,7 @@ String StyledMarkupAccumulator::takeResults() void StyledMarkupAccumulator::appendText(StringBuilder& out, const Text& text) { - const bool parentIsTextarea = text.parentElement() && isHTMLTextAreaElement(text.parentElement()); + const bool parentIsTextarea = is<HTMLTextAreaElement>(text.parentElement()); const bool wrappingSpan = shouldApplyWrappingStyle(text) && !parentIsTextarea; if (wrappingSpan) { RefPtr<EditingStyle> wrappingStyle = m_wrappingStyle->copy(); @@ -240,7 +250,7 @@ void StyledMarkupAccumulator::appendText(StringBuilder& out, const Text& text) if (!shouldAnnotate() || parentIsTextarea) MarkupAccumulator::appendText(out, text); else { - const bool useRenderedText = !enclosingNodeWithTag(firstPositionInNode(const_cast<Text*>(&text)), selectTag); + const bool useRenderedText = !enclosingElementWithTag(firstPositionInNode(const_cast<Text*>(&text)), selectTag); String content = useRenderedText ? renderedText(text, m_range) : stringValueForRange(text, m_range); StringBuilder buffer; appendCharactersReplacingEntities(buffer, content, 0, content.length(), EntityMaskInPCDATA); @@ -253,21 +263,24 @@ void StyledMarkupAccumulator::appendText(StringBuilder& out, const Text& text) String StyledMarkupAccumulator::renderedText(const Node& node, const Range* range) { - if (!node.isTextNode()) + if (!is<Text>(node)) return String(); - const Text& textNode = toText(node); + const Text& textNode = downcast<Text>(node); unsigned startOffset = 0; unsigned endOffset = textNode.length(); - if (range && &node == range->startContainer()) + TextIteratorBehavior behavior = TextIteratorDefaultBehavior; + if (range && &node == &range->startContainer()) startOffset = range->startOffset(); - if (range && &node == range->endContainer()) + if (range && &node == &range->endContainer()) endOffset = range->endOffset(); + else if (range) + behavior = TextIteratorBehavesAsIfNodesFollowing; Position start = createLegacyEditingPosition(const_cast<Node*>(&node), startOffset); Position end = createLegacyEditingPosition(const_cast<Node*>(&node), endOffset); - return plainText(Range::create(node.document(), start, end).get()); + return plainText(Range::create(node.document(), start, end).ptr(), behavior); } String StyledMarkupAccumulator::stringValueForRange(const Node& node, const Range* range) @@ -276,18 +289,36 @@ String StyledMarkupAccumulator::stringValueForRange(const Node& node, const Rang return node.nodeValue(); String nodeValue = node.nodeValue(); - if (&node == range->endContainer()) + if (&node == &range->endContainer()) nodeValue.truncate(range->endOffset()); - if (&node == range->startContainer()) + if (&node == &range->startContainer()) nodeValue.remove(0, range->startOffset()); return nodeValue; } +void StyledMarkupAccumulator::appendCustomAttributes(StringBuilder& out, const Element&element, Namespaces* namespaces) +{ +#if ENABLE(ATTACHMENT_ELEMENT) + if (!is<HTMLAttachmentElement>(element)) + return; + + const HTMLAttachmentElement& attachment = downcast<HTMLAttachmentElement>(element); + if (attachment.file()) + appendAttribute(out, element, Attribute(webkitattachmentpathAttr, attachment.file()->path()), namespaces); +#else + UNUSED_PARAM(out); + UNUSED_PARAM(element); + UNUSED_PARAM(namespaces); +#endif +} + void StyledMarkupAccumulator::appendElement(StringBuilder& out, const Element& element, bool addDisplayInline, RangeFullySelectsNode rangeFullySelectsNode) { const bool documentIsHTML = element.document().isHTMLDocument(); appendOpenTag(out, element, 0); + appendCustomAttributes(out, element, nullptr); + const bool shouldAnnotateOrForceInline = element.isHTMLElement() && (shouldAnnotate() || addDisplayInline); const bool shouldOverrideStyleAttr = shouldAnnotateOrForceInline || shouldApplyWrappingStyle(element); if (element.hasAttributes()) { @@ -309,15 +340,20 @@ void StyledMarkupAccumulator::appendElement(StringBuilder& out, const Element& e } else newInlineStyle = EditingStyle::create(); - if (element.isStyledElement() && toStyledElement(element).inlineStyle()) - newInlineStyle->overrideWithStyle(toStyledElement(element).inlineStyle()); + if (is<StyledElement>(element) && downcast<StyledElement>(element).inlineStyle()) + newInlineStyle->overrideWithStyle(downcast<StyledElement>(element).inlineStyle()); if (shouldAnnotateOrForceInline) { if (shouldAnnotate()) - newInlineStyle->mergeStyleFromRulesForSerialization(toHTMLElement(const_cast<Element*>(&element))); + newInlineStyle->mergeStyleFromRulesForSerialization(downcast<HTMLElement>(const_cast<Element*>(&element))); if (addDisplayInline) newInlineStyle->forceInline(); + + if (m_needsPositionStyleConversion) { + m_needRelativeStyleWrapper |= newInlineStyle->convertPositionStyle(); + m_needClearingDiv |= newInlineStyle->isFloating(); + } // If the node is not fully selected by the range, then we don't want to keep styles that affect its relationship to the nodes around it // only the ones that affect it and the nodes within it. @@ -353,7 +389,7 @@ Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, No const bool shouldEmit = traversalMode == EmitString; Vector<Node*> ancestorsToClose; Node* next; - Node* lastClosed = 0; + Node* lastClosed = nullptr; for (Node* n = startNode; n != pastEnd; n = next) { // According to <rdar://problem/5730668>, it is possible for n to blow // past pastEnd and become null here. This shouldn't be possible. @@ -363,17 +399,18 @@ Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, No if (!n) break; - next = NodeTraversal::next(n); + next = NodeTraversal::next(*n); bool openedTag = false; - if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd) + if (isBlock(n) && canHaveChildrenForEditing(*n) && next == pastEnd) { // Don't write out empty block containers that aren't fully selected. continue; + } - if (!n->renderer() && !enclosingNodeWithTag(firstPositionInOrBeforeNode(n), selectTag)) { - next = NodeTraversal::nextSkippingChildren(n); + if (!n->renderer() && !enclosingElementWithTag(firstPositionInOrBeforeNode(n), selectTag)) { + next = NodeTraversal::nextSkippingChildren(*n); // Don't skip over pastEnd. - if (pastEnd && pastEnd->isDescendantOf(n)) + if (pastEnd && pastEnd->isDescendantOf(*n)) next = pastEnd; } else { // Add the node to the markup if we're not skipping the descendants @@ -381,7 +418,7 @@ Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, No appendStartTag(*n); // If node has no children, close the tag now. - if (!n->childNodeCount()) { + if (!n->hasChildNodes()) { if (shouldEmit) appendEndTag(*n); lastClosed = n; @@ -415,7 +452,7 @@ Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, No if (!parent->renderer()) continue; // or b) ancestors that we never encountered during a pre-order traversal starting at startNode: - ASSERT(startNode->isDescendantOf(parent)); + ASSERT(startNode->isDescendantOf(*parent)); if (shouldEmit) wrapWithNode(*parent); lastClosed = parent; @@ -430,11 +467,11 @@ Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, No static Node* ancestorToRetainStructureAndAppearanceForBlock(Node* commonAncestorBlock) { if (!commonAncestorBlock) - return 0; + return nullptr; if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) { ContainerNode* table = commonAncestorBlock->parentNode(); - while (table && !isHTMLTableElement(table)) + while (table && !is<HTMLTableElement>(*table)) table = table->parentNode(); return table; @@ -443,7 +480,7 @@ static Node* ancestorToRetainStructureAndAppearanceForBlock(Node* commonAncestor if (isNonTableCellHTMLBlockElement(commonAncestorBlock)) return commonAncestorBlock; - return 0; + return nullptr; } static inline Node* ancestorToRetainStructureAndAppearance(Node* commonAncestor) @@ -458,9 +495,9 @@ static bool propertyMissingOrEqualToNone(StyleProperties* style, CSSPropertyID p RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID); if (!value) return true; - if (!value->isPrimitiveValue()) + if (!is<CSSPrimitiveValue>(*value)) return false; - return toCSSPrimitiveValue(value.get())->getValueID() == CSSValueNone; + return downcast<CSSPrimitiveValue>(*value).valueID() == CSSValueNone; } static bool needInterchangeNewlineAfter(const VisiblePosition& v) @@ -472,17 +509,17 @@ static bool needInterchangeNewlineAfter(const VisiblePosition& v) return isEndOfParagraph(v) && isStartOfParagraph(next) && !(upstreamNode->hasTagName(brTag) && upstreamNode == downstreamNode); } -static PassRefPtr<EditingStyle> styleFromMatchedRulesAndInlineDecl(const Node* node) +static RefPtr<EditingStyle> styleFromMatchedRulesAndInlineDecl(const Node* node) { if (!node->isHTMLElement()) - return 0; + return nullptr; // FIXME: Having to const_cast here is ugly, but it is quite a bit of work to untangle // the non-const-ness of styleFromMatchedRulesForElement. HTMLElement* element = const_cast<HTMLElement*>(static_cast<const HTMLElement*>(node)); RefPtr<EditingStyle> style = EditingStyle::create(element->inlineStyle()); style->mergeStyleFromRules(element); - return style.release(); + return style; } static bool isElementPresentational(const Node* node) @@ -493,18 +530,18 @@ static bool isElementPresentational(const Node* node) static Node* highestAncestorToWrapMarkup(const Range* range, EAnnotateForInterchange shouldAnnotate) { - Node* commonAncestor = range->commonAncestorContainer(IGNORE_EXCEPTION); + auto* commonAncestor = range->commonAncestorContainer(); ASSERT(commonAncestor); - Node* specialCommonAncestor = 0; + Node* specialCommonAncestor = nullptr; if (shouldAnnotate == AnnotateForInterchange) { // Include ancestors that aren't completely inside the range but are required to retain // the structure and appearance of the copied markup. specialCommonAncestor = ancestorToRetainStructureAndAppearance(commonAncestor); - if (Node* parentListNode = enclosingNodeOfType(firstPositionInOrBeforeNode(range->firstNode()), isListItem)) { - if (WebCore::areRangesEqual(VisibleSelection::selectionFromContentsOfNode(parentListNode).toNormalizedRange().get(), range)) { + if (auto* parentListNode = enclosingNodeOfType(firstPositionInOrBeforeNode(range->firstNode()), isListItem)) { + if (!editingIgnoresContent(*parentListNode) && WebCore::areRangesEqual(VisibleSelection::selectionFromContentsOfNode(parentListNode).toNormalizedRange().get(), range)) { specialCommonAncestor = parentListNode->parentNode(); - while (specialCommonAncestor && !isListElement(specialCommonAncestor)) + while (specialCommonAncestor && !isListHTMLElement(specialCommonAncestor)) specialCommonAncestor = specialCommonAncestor->parentNode(); } } @@ -514,7 +551,7 @@ static Node* highestAncestorToWrapMarkup(const Range* range, EAnnotateForInterch specialCommonAncestor = highestMailBlockquote; } - Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor; + auto* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor; if (checkAncestor->renderer() && checkAncestor->renderer()->containingBlock()) { Node* newSpecialCommonAncestor = highestEnclosingNodeOfType(firstPositionInNode(checkAncestor), &isElementPresentational, CanCrossEditingBoundary, checkAncestor->renderer()->containingBlock()->element()); if (newSpecialCommonAncestor) @@ -530,7 +567,7 @@ static Node* highestAncestorToWrapMarkup(const Range* range, EAnnotateForInterch if (!specialCommonAncestor && isTabSpanNode(commonAncestor)) specialCommonAncestor = commonAncestor; - if (Node *enclosingAnchor = enclosingNodeWithTag(firstPositionInNode(specialCommonAncestor ? specialCommonAncestor : commonAncestor), aTag)) + if (auto* enclosingAnchor = enclosingElementWithTag(firstPositionInNode(specialCommonAncestor ? specialCommonAncestor : commonAncestor), aTag)) specialCommonAncestor = enclosingAnchor; return specialCommonAncestor; @@ -538,33 +575,36 @@ static Node* highestAncestorToWrapMarkup(const Range* range, EAnnotateForInterch // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? // FIXME: At least, annotation and style info should probably not be included in range.markupString() -static String createMarkupInternal(Document& document, const Range& range, const Range& updatedRange, Vector<Node*>* nodes, +static String createMarkupInternal(Document& document, const Range& range, Vector<Node*>* nodes, EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs) { - DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, (ASCIILiteral("<br class=\"" AppleInterchangeNewline "\">"))); + static NeverDestroyed<const String> interchangeNewlineString(ASCIILiteral("<br class=\"" AppleInterchangeNewline "\">")); - bool collapsed = updatedRange.collapsed(ASSERT_NO_EXCEPTION); + bool collapsed = range.collapsed(); if (collapsed) return emptyString(); - Node* commonAncestor = updatedRange.commonAncestorContainer(ASSERT_NO_EXCEPTION); + Node* commonAncestor = range.commonAncestorContainer(); if (!commonAncestor) return emptyString(); document.updateLayoutIgnorePendingStylesheets(); - Node* body = enclosingNodeWithTag(firstPositionInNode(commonAncestor), bodyTag); - Node* fullySelectedRoot = 0; + auto* body = enclosingElementWithTag(firstPositionInNode(commonAncestor), bodyTag); + Element* fullySelectedRoot = nullptr; // FIXME: Do this for all fully selected blocks, not just the body. - if (body && areRangesEqual(VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange().get(), &range)) + if (body && VisiblePosition(firstPositionInNode(body)) == VisiblePosition(range.startPosition()) + && VisiblePosition(lastPositionInNode(body)) == VisiblePosition(range.endPosition())) fullySelectedRoot = body; - Node* specialCommonAncestor = highestAncestorToWrapMarkup(&updatedRange, shouldAnnotate); + Node* specialCommonAncestor = highestAncestorToWrapMarkup(&range, shouldAnnotate); - StyledMarkupAccumulator accumulator(nodes, shouldResolveURLs, shouldAnnotate, &updatedRange, specialCommonAncestor); - Node* pastEnd = updatedRange.pastLastNode(); + bool needsPositionStyleConversion = body && fullySelectedRoot == body + && document.settings().shouldConvertPositionStyleOnCopy(); + StyledMarkupAccumulator accumulator(nodes, shouldResolveURLs, shouldAnnotate, &range, needsPositionStyleConversion, specialCommonAncestor); + Node* pastEnd = range.pastLastNode(); - Node* startNode = updatedRange.firstNode(); - VisiblePosition visibleStart(updatedRange.startPosition(), VP_DEFAULT_AFFINITY); - VisiblePosition visibleEnd(updatedRange.endPosition(), VP_DEFAULT_AFFINITY); + Node* startNode = range.firstNode(); + VisiblePosition visibleStart(range.startPosition(), VP_DEFAULT_AFFINITY); + VisiblePosition visibleEnd(range.endPosition(), VP_DEFAULT_AFFINITY); if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleStart)) { if (visibleStart == visibleEnd.previous()) return interchangeNewlineString; @@ -572,7 +612,7 @@ static String createMarkupInternal(Document& document, const Range& range, const accumulator.appendString(interchangeNewlineString); startNode = visibleStart.next().deepEquivalent().deprecatedNode(); - if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0, ASSERT_NO_EXCEPTION) >= 0) + if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0).releaseReturnValue() >= 0) return interchangeNewlineString; } @@ -587,8 +627,8 @@ static String createMarkupInternal(Document& document, const Range& range, const // Bring the background attribute over, but not as an attribute because a background attribute on a div // appears to have no effect. if ((!fullySelectedRootStyle || !fullySelectedRootStyle->style() || !fullySelectedRootStyle->style()->getPropertyCSSValue(CSSPropertyBackgroundImage)) - && toElement(fullySelectedRoot)->hasAttribute(backgroundAttr)) - fullySelectedRootStyle->style()->setProperty(CSSPropertyBackgroundImage, "url('" + toElement(fullySelectedRoot)->getAttribute(backgroundAttr) + "')"); + && fullySelectedRoot->hasAttributeWithoutSynchronization(backgroundAttr)) + fullySelectedRootStyle->style()->setProperty(CSSPropertyBackgroundImage, "url('" + fullySelectedRoot->getAttribute(backgroundAttr) + "')"); if (fullySelectedRootStyle->style()) { // Reset the CSS properties to avoid an assertion error in addStyleMarkup(). @@ -608,12 +648,18 @@ static String createMarkupInternal(Document& document, const Range& range, const if (nodes) nodes->append(ancestor); - lastClosed = ancestor; - if (ancestor == specialCommonAncestor) break; } } + + if (accumulator.needRelativeStyleWrapper() && needsPositionStyleConversion) { + if (accumulator.needClearingDiv()) + accumulator.appendString("<div style=\"clear: both;\"></div>"); + RefPtr<EditingStyle> positionRelativeStyle = styleFromMatchedRulesAndInlineDecl(body); + positionRelativeStyle->style()->setProperty(CSSPropertyPosition, CSSValueRelative); + accumulator.wrapWithStyleNode(positionRelativeStyle->style(), document, true); + } // FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally. if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleEnd.previous())) @@ -624,62 +670,46 @@ static String createMarkupInternal(Document& document, const Range& range, const String createMarkup(const Range& range, Vector<Node*>* nodes, EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs) { - Document& document = range.ownerDocument(); - const Range* updatedRange = ⦥ - -#if ENABLE(DELETION_UI) - // Disable the delete button so it's elements are not serialized into the markup, - // but make sure neither endpoint is inside the delete user interface. - Frame* frame = document.frame(); - DeleteButtonControllerDisableScope deleteButtonControllerDisableScope(frame); - - RefPtr<Range> updatedRangeRef; - if (frame) { - updatedRangeRef = frame->editor().avoidIntersectionWithDeleteButtonController(&range); - updatedRange = updatedRangeRef.get(); - if (!updatedRange) - return emptyString(); - } -#endif - - return createMarkupInternal(document, range, *updatedRange, nodes, shouldAnnotate, convertBlocksToInlines, shouldResolveURLs); + return createMarkupInternal(range.ownerDocument(), range, nodes, shouldAnnotate, convertBlocksToInlines, shouldResolveURLs); } -PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document& document, const String& markup, const String& baseURL, ParserContentPolicy parserContentPolicy) +Ref<DocumentFragment> createFragmentFromMarkup(Document& document, const String& markup, const String& baseURL, ParserContentPolicy parserContentPolicy) { // We use a fake body element here to trick the HTML parser to using the InBody insertion mode. - RefPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(document); - RefPtr<DocumentFragment> fragment = DocumentFragment::create(document); + auto fakeBody = HTMLBodyElement::create(document); + auto fragment = DocumentFragment::create(document); + + fragment->parseHTML(markup, fakeBody.ptr(), parserContentPolicy); - fragment->parseHTML(markup, fakeBody.get(), parserContentPolicy); +#if ENABLE(ATTACHMENT_ELEMENT) + // When creating a fragment we must strip the webkit-attachment-path attribute after restoring the File object. + Vector<Ref<HTMLAttachmentElement>> attachments; + for (auto& attachment : descendantsOfType<HTMLAttachmentElement>(fragment)) + attachments.append(attachment); + for (auto& attachment : attachments) { + attachment->setFile(File::create(attachment->attributeWithoutSynchronization(webkitattachmentpathAttr)).ptr()); + attachment->removeAttribute(webkitattachmentpathAttr); + } +#endif if (!baseURL.isEmpty() && baseURL != blankURL() && baseURL != document.baseURL()) - completeURLs(fragment.get(), baseURL); + completeURLs(fragment.ptr(), baseURL); - return fragment.release(); + return fragment; } String createMarkup(const Node& node, EChildrenOnly childrenOnly, Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, Vector<QualifiedName>* tagNamesToSkip, EFragmentSerialization fragmentSerialization) { - HTMLElement* deleteButtonContainerElement = 0; -#if ENABLE(DELETION_UI) - if (Frame* frame = node.document().frame()) { - deleteButtonContainerElement = frame->editor().deleteButtonController().containerElement(); - if (node.isDescendantOf(deleteButtonContainerElement)) - return emptyString(); - } -#endif - MarkupAccumulator accumulator(nodes, shouldResolveURLs, 0, fragmentSerialization); - return accumulator.serializeNodes(const_cast<Node&>(node), deleteButtonContainerElement, childrenOnly, tagNamesToSkip); + return accumulator.serializeNodes(const_cast<Node&>(node), childrenOnly, tagNamesToSkip); } -static void fillContainerFromString(ContainerNode* paragraph, const String& string) +static void fillContainerFromString(ContainerNode& paragraph, const String& string) { - Document& document = paragraph->document(); + Document& document = paragraph.document(); if (string.isEmpty()) { - paragraph->appendChild(createBlockPlaceholderElement(document), ASSERT_NO_EXCEPTION); + paragraph.appendChild(createBlockPlaceholderElement(document)); return; } @@ -696,11 +726,11 @@ static void fillContainerFromString(ContainerNode* paragraph, const String& stri // append the non-tab textual part if (!s.isEmpty()) { if (!tabText.isEmpty()) { - paragraph->appendChild(createTabSpanElement(document, tabText), ASSERT_NO_EXCEPTION); + paragraph.appendChild(createTabSpanElement(document, tabText)); tabText = emptyString(); } - RefPtr<Node> textNode = document.createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries)); - paragraph->appendChild(textNode.release(), ASSERT_NO_EXCEPTION); + Ref<Node> textNode = document.createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries)); + paragraph.appendChild(textNode); } // there is a tab after every entry, except the last entry @@ -708,21 +738,34 @@ static void fillContainerFromString(ContainerNode* paragraph, const String& stri if (i + 1 != numEntries) tabText.append('\t'); else if (!tabText.isEmpty()) - paragraph->appendChild(createTabSpanElement(document, tabText), ASSERT_NO_EXCEPTION); + paragraph.appendChild(createTabSpanElement(document, tabText)); first = false; } } -bool isPlainTextMarkup(Node *node) +bool isPlainTextMarkup(Node* node) { - if (!node->isElementNode() || !node->hasTagName(divTag) || toElement(node)->hasAttributes()) + ASSERT(node); + if (!is<HTMLDivElement>(*node)) + return false; + + HTMLDivElement& element = downcast<HTMLDivElement>(*node); + if (element.hasAttributes()) + return false; + + Node* firstChild = element.firstChild(); + if (!firstChild) return false; + + Node* secondChild = firstChild->nextSibling(); + if (!secondChild) + return firstChild->isTextNode() || firstChild->firstChild(); - if (node->childNodeCount() == 1 && (node->firstChild()->isTextNode() || (node->firstChild()->firstChild()))) - return true; + if (secondChild->nextSibling()) + return false; - return (node->childNodeCount() == 2 && isTabSpanTextNode(node->firstChild()->firstChild()) && node->firstChild()->nextSibling()->isTextNode()); + return isTabSpanTextNode(firstChild->firstChild()) && secondChild->isTextNode(); } static bool contextPreservesNewline(const Range& context) @@ -735,37 +778,37 @@ static bool contextPreservesNewline(const Range& context) return container->renderer()->style().preserveNewline(); } -PassRefPtr<DocumentFragment> createFragmentFromText(Range& context, const String& text) +Ref<DocumentFragment> createFragmentFromText(Range& context, const String& text) { Document& document = context.ownerDocument(); - RefPtr<DocumentFragment> fragment = document.createDocumentFragment(); + Ref<DocumentFragment> fragment = document.createDocumentFragment(); if (text.isEmpty()) - return fragment.release(); + return fragment; String string = text; string.replace("\r\n", "\n"); string.replace('\r', '\n'); if (contextPreservesNewline(context)) { - fragment->appendChild(document.createTextNode(string), ASSERT_NO_EXCEPTION); + fragment->appendChild(document.createTextNode(string)); if (string.endsWith('\n')) { - RefPtr<Element> element = createBreakElement(document); - element->setAttribute(classAttr, AppleInterchangeNewline); - fragment->appendChild(element.release(), ASSERT_NO_EXCEPTION); + auto element = HTMLBRElement::create(document); + element->setAttributeWithoutSynchronization(classAttr, AppleInterchangeNewline); + fragment->appendChild(element); } - return fragment.release(); + return fragment; } // A string with no newlines gets added inline, rather than being put into a paragraph. if (string.find('\n') == notFound) { - fillContainerFromString(fragment.get(), string); - return fragment.release(); + fillContainerFromString(fragment, string); + return fragment; } // Break string into paragraphs. Extra line breaks turn into empty paragraphs. Node* blockNode = enclosingBlock(context.firstNode()); - Element* block = toElement(blockNode); + Element* block = downcast<Element>(blockNode); bool useClonesOfEnclosingBlock = blockNode && blockNode->isElementNode() && !block->hasTagName(bodyTag) @@ -782,21 +825,21 @@ PassRefPtr<DocumentFragment> createFragmentFromText(Range& context, const String RefPtr<Element> element; if (s.isEmpty() && i + 1 == numLines) { // For last line, use the "magic BR" rather than a P. - element = createBreakElement(document); - element->setAttribute(classAttr, AppleInterchangeNewline); + element = HTMLBRElement::create(document); + element->setAttributeWithoutSynchronization(classAttr, AppleInterchangeNewline); } else if (useLineBreak) { - element = createBreakElement(document); - fillContainerFromString(fragment.get(), s); + element = HTMLBRElement::create(document); + fillContainerFromString(fragment, s); } else { if (useClonesOfEnclosingBlock) - element = block->cloneElementWithoutChildren(); + element = block->cloneElementWithoutChildren(document); else element = createDefaultParagraphElement(document); - fillContainerFromString(element.get(), s); + fillContainerFromString(*element, s); } - fragment->appendChild(element.release(), ASSERT_NO_EXCEPTION); + fragment->appendChild(*element); } - return fragment.release(); + return fragment; } String documentTypeString(const Document& document) @@ -821,81 +864,73 @@ String createFullMarkup(const Node& node) String createFullMarkup(const Range& range) { - Node* node = range.startContainer(); - if (!node) - return String(); - // FIXME: This is always "for interchange". Is that right? - return documentTypeString(node->document()) + createMarkup(range, 0, AnnotateForInterchange); + return documentTypeString(range.startContainer().document()) + createMarkup(range, 0, AnnotateForInterchange); } String urlToMarkup(const URL& url, const String& title) { StringBuilder markup; - markup.append("<a href=\""); + markup.appendLiteral("<a href=\""); markup.append(url.string()); - markup.append("\">"); + markup.appendLiteral("\">"); MarkupAccumulator::appendCharactersReplacingEntities(markup, title, 0, title.length(), EntityMaskInPCDATA); - markup.append("</a>"); + markup.appendLiteral("</a>"); return markup.toString(); } -PassRefPtr<DocumentFragment> createFragmentForInnerOuterHTML(const String& markup, Element* contextElement, ParserContentPolicy parserContentPolicy, ExceptionCode& ec) +ExceptionOr<Ref<DocumentFragment>> createFragmentForInnerOuterHTML(Element& contextElement, const String& markup, ParserContentPolicy parserContentPolicy) { - Document* document = &contextElement->document(); -#if ENABLE(TEMPLATE_ELEMENT) - if (contextElement->hasTagName(templateTag)) - document = document->ensureTemplateDocument(); -#endif - RefPtr<DocumentFragment> fragment = DocumentFragment::create(*document); + auto* document = &contextElement.document(); + if (contextElement.hasTagName(templateTag)) + document = &document->ensureTemplateDocument(); + auto fragment = DocumentFragment::create(*document); if (document->isHTMLDocument()) { - fragment->parseHTML(markup, contextElement, parserContentPolicy); - return fragment; + fragment->parseHTML(markup, &contextElement, parserContentPolicy); + return WTFMove(fragment); } - bool wasValid = fragment->parseXML(markup, contextElement, parserContentPolicy); - if (!wasValid) { - ec = SYNTAX_ERR; - return 0; - } - return fragment.release(); + bool wasValid = fragment->parseXML(markup, &contextElement, parserContentPolicy); + if (!wasValid) + return Exception { SYNTAX_ERR }; + return WTFMove(fragment); } -PassRefPtr<DocumentFragment> createFragmentForTransformToFragment(const String& sourceString, const String& sourceMIMEType, Document* outputDoc) +RefPtr<DocumentFragment> createFragmentForTransformToFragment(Document& outputDoc, const String& sourceString, const String& sourceMIMEType) { - RefPtr<DocumentFragment> fragment = outputDoc->createDocumentFragment(); + RefPtr<DocumentFragment> fragment = outputDoc.createDocumentFragment(); if (sourceMIMEType == "text/html") { // As far as I can tell, there isn't a spec for how transformToFragment is supposed to work. // Based on the documentation I can find, it looks like we want to start parsing the fragment in the InBody insertion mode. // Unfortunately, that's an implementation detail of the parser. // We achieve that effect here by passing in a fake body element as context for the fragment. - RefPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(*outputDoc); + RefPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(outputDoc); fragment->parseHTML(sourceString, fakeBody.get()); } else if (sourceMIMEType == "text/plain") - fragment->parserAppendChild(Text::create(*outputDoc, sourceString)); + fragment->parserAppendChild(Text::create(outputDoc, sourceString)); else { bool successfulParse = fragment->parseXML(sourceString, 0); if (!successfulParse) - return 0; + return nullptr; } // FIXME: Do we need to mess with URLs here? - return fragment.release(); + return fragment; } static Vector<Ref<HTMLElement>> collectElementsToRemoveFromFragment(ContainerNode& container) { Vector<Ref<HTMLElement>> toRemove; for (auto& element : childrenOfType<HTMLElement>(container)) { - if (isHTMLHtmlElement(element)) { + if (is<HTMLHtmlElement>(element)) { toRemove.append(element); collectElementsToRemoveFromFragment(element); continue; } - if (isHTMLHeadElement(element) || isHTMLBodyElement(element)) + if (is<HTMLHeadElement>(element) || is<HTMLBodyElement>(element)) toRemove.append(element); } return toRemove; @@ -906,94 +941,80 @@ static void removeElementFromFragmentPreservingChildren(DocumentFragment& fragme RefPtr<Node> nextChild; for (RefPtr<Node> child = element.firstChild(); child; child = nextChild) { nextChild = child->nextSibling(); - element.removeChild(child.get(), ASSERT_NO_EXCEPTION); - fragment.insertBefore(child, &element, ASSERT_NO_EXCEPTION); + element.removeChild(*child); + fragment.insertBefore(*child, &element); } - fragment.removeChild(&element, ASSERT_NO_EXCEPTION); + fragment.removeChild(element); } -PassRefPtr<DocumentFragment> createContextualFragment(const String& markup, HTMLElement* element, ParserContentPolicy parserContentPolicy, ExceptionCode& ec) +ExceptionOr<Ref<DocumentFragment>> createContextualFragment(Element& element, const String& markup, ParserContentPolicy parserContentPolicy) { - ASSERT(element); - if (element->ieForbidsInsertHTML()) { - ec = NOT_SUPPORTED_ERR; - return 0; - } - - if (element->hasLocalName(colTag) || element->hasLocalName(colgroupTag) || element->hasLocalName(framesetTag) - || element->hasLocalName(headTag) || element->hasLocalName(styleTag) || element->hasLocalName(titleTag)) { - ec = NOT_SUPPORTED_ERR; - return 0; - } + auto result = createFragmentForInnerOuterHTML(element, markup, parserContentPolicy); + if (result.hasException()) + return result.releaseException(); - RefPtr<DocumentFragment> fragment = createFragmentForInnerOuterHTML(markup, element, parserContentPolicy, ec); - if (!fragment) - return 0; + auto fragment = result.releaseReturnValue(); // We need to pop <html> and <body> elements and remove <head> to // accommodate folks passing complete HTML documents to make the // child of an element. - auto toRemove = collectElementsToRemoveFromFragment(*fragment); - for (unsigned i = 0; i < toRemove.size(); ++i) - removeElementFromFragmentPreservingChildren(*fragment, toRemove[i].get()); + auto toRemove = collectElementsToRemoveFromFragment(fragment); + for (auto& element : toRemove) + removeElementFromFragmentPreservingChildren(fragment, element); - return fragment.release(); + return WTFMove(fragment); } -static inline bool hasOneChild(ContainerNode* node) +static inline bool hasOneChild(ContainerNode& node) { - Node* firstChild = node->firstChild(); + Node* firstChild = node.firstChild(); return firstChild && !firstChild->nextSibling(); } -static inline bool hasOneTextChild(ContainerNode* node) +static inline bool hasOneTextChild(ContainerNode& node) { - return hasOneChild(node) && node->firstChild()->isTextNode(); + return hasOneChild(node) && node.firstChild()->isTextNode(); } -void replaceChildrenWithFragment(ContainerNode& container, PassRefPtr<DocumentFragment> fragment, ExceptionCode& ec) +static inline bool hasMutationEventListeners(const Document& document) { - Ref<ContainerNode> containerNode(container); - ChildListMutationScope mutation(containerNode.get()); - - if (!fragment->firstChild()) { - containerNode->removeChildren(); - return; - } - - if (hasOneTextChild(&containerNode.get()) && hasOneTextChild(fragment.get())) { - toText(containerNode->firstChild())->setData(toText(fragment->firstChild())->data(), ec); - return; - } - - if (hasOneChild(&containerNode.get())) { - containerNode->replaceChild(fragment, containerNode->firstChild(), ec); - return; - } + return document.hasListenerType(Document::DOMSUBTREEMODIFIED_LISTENER) + || document.hasListenerType(Document::DOMNODEINSERTED_LISTENER) + || document.hasListenerType(Document::DOMNODEREMOVED_LISTENER) + || document.hasListenerType(Document::DOMNODEREMOVEDFROMDOCUMENT_LISTENER) + || document.hasListenerType(Document::DOMCHARACTERDATAMODIFIED_LISTENER); +} - containerNode->removeChildren(); - containerNode->appendChild(fragment, ec); +// We can use setData instead of replacing Text node as long as script can't observe the difference. +static inline bool canUseSetDataOptimization(const Text& containerChild, const ChildListMutationScope& mutationScope) +{ + bool authorScriptMayHaveReference = containerChild.refCount(); + return !authorScriptMayHaveReference && !mutationScope.canObserve() && !hasMutationEventListeners(containerChild.document()); } -void replaceChildrenWithText(ContainerNode& container, const String& text, ExceptionCode& ec) +ExceptionOr<void> replaceChildrenWithFragment(ContainerNode& container, Ref<DocumentFragment>&& fragment) { Ref<ContainerNode> containerNode(container); - ChildListMutationScope mutation(containerNode.get()); + ChildListMutationScope mutation(containerNode); - if (hasOneTextChild(&containerNode.get())) { - toText(containerNode->firstChild())->setData(text, ec); - return; + if (!fragment->firstChild()) { + containerNode->removeChildren(); + return { }; } - RefPtr<Text> textNode = Text::create(containerNode->document(), text); + auto* containerChild = containerNode->firstChild(); + if (containerChild && !containerChild->nextSibling()) { + if (is<Text>(*containerChild) && hasOneTextChild(fragment) && canUseSetDataOptimization(downcast<Text>(*containerChild), mutation)) { + ASSERT(!fragment->firstChild()->refCount()); + downcast<Text>(*containerChild).setData(downcast<Text>(*fragment->firstChild()).data()); + return { }; + } - if (hasOneChild(&containerNode.get())) { - containerNode->replaceChild(textNode.release(), containerNode->firstChild(), ec); - return; + return containerNode->replaceChild(fragment, *containerChild); } containerNode->removeChildren(); - containerNode->appendChild(textNode.release(), ec); + return containerNode->appendChild(fragment); } } diff --git a/Source/WebCore/editing/markup.h b/Source/WebCore/editing/markup.h index 65b72adc3..8d4fe389d 100644 --- a/Source/WebCore/editing/markup.h +++ b/Source/WebCore/editing/markup.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2004 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * 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 COMPUTER, INC. OR + * 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 @@ -23,9 +23,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef markup_h -#define markup_h +#pragma once +#include "ExceptionOr.h" #include "FragmentScriptingPermission.h" #include "HTMLInterchange.h" #include <wtf/Forward.h> @@ -43,35 +43,29 @@ class Node; class QualifiedName; class Range; -typedef int ExceptionCode; - enum EChildrenOnly { IncludeNode, ChildrenOnly }; enum EAbsoluteURLs { DoNotResolveURLs, ResolveAllURLs, ResolveNonLocalURLs }; enum EFragmentSerialization { HTMLFragmentSerialization, XMLFragmentSerialization }; -PassRefPtr<DocumentFragment> createFragmentFromText(Range& context, const String& text); -PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document&, const String& markup, const String& baseURL, ParserContentPolicy = AllowScriptingContent); -PassRefPtr<DocumentFragment> createFragmentForInnerOuterHTML(const String&, Element*, ParserContentPolicy, ExceptionCode&); -PassRefPtr<DocumentFragment> createFragmentForTransformToFragment(const String&, const String& sourceMIMEType, Document* outputDoc); -PassRefPtr<DocumentFragment> createContextualFragment(const String&, HTMLElement*, ParserContentPolicy, ExceptionCode&); +WEBCORE_EXPORT Ref<DocumentFragment> createFragmentFromText(Range& context, const String& text); +WEBCORE_EXPORT Ref<DocumentFragment> createFragmentFromMarkup(Document&, const String& markup, const String& baseURL, ParserContentPolicy = AllowScriptingContent); +ExceptionOr<Ref<DocumentFragment>> createFragmentForInnerOuterHTML(Element&, const String& markup, ParserContentPolicy); +RefPtr<DocumentFragment> createFragmentForTransformToFragment(Document&, const String& sourceString, const String& sourceMIMEType); +ExceptionOr<Ref<DocumentFragment>> createContextualFragment(Element&, const String& markup, ParserContentPolicy); bool isPlainTextMarkup(Node*); -// These methods are used by HTMLElement & ShadowRoot to replace the -// children with respected fragment/text. -void replaceChildrenWithFragment(ContainerNode&, PassRefPtr<DocumentFragment>, ExceptionCode&); -void replaceChildrenWithText(ContainerNode&, const String&, ExceptionCode&); +// These methods are used by HTMLElement & ShadowRoot to replace the children with respected fragment/text. +ExceptionOr<void> replaceChildrenWithFragment(ContainerNode&, Ref<DocumentFragment>&&); -String createMarkup(const Range&, Vector<Node*>* = 0, EAnnotateForInterchange = DoNotAnnotateForInterchange, bool convertBlocksToInlines = false, EAbsoluteURLs = DoNotResolveURLs); -String createMarkup(const Node&, EChildrenOnly = IncludeNode, Vector<Node*>* = 0, EAbsoluteURLs = DoNotResolveURLs, Vector<QualifiedName>* tagNamesToSkip = 0, EFragmentSerialization = HTMLFragmentSerialization); +String createMarkup(const Range&, Vector<Node*>* = nullptr, EAnnotateForInterchange = DoNotAnnotateForInterchange, bool convertBlocksToInlines = false, EAbsoluteURLs = DoNotResolveURLs); +String createMarkup(const Node&, EChildrenOnly = IncludeNode, Vector<Node*>* = nullptr, EAbsoluteURLs = DoNotResolveURLs, Vector<QualifiedName>* tagNamesToSkip = nullptr, EFragmentSerialization = HTMLFragmentSerialization); -String createFullMarkup(const Node&); -String createFullMarkup(const Range&); +WEBCORE_EXPORT String createFullMarkup(const Node&); +WEBCORE_EXPORT String createFullMarkup(const Range&); String urlToMarkup(const URL&, const String& title); String documentTypeString(const Document&); } - -#endif // markup_h |