From 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c Mon Sep 17 00:00:00 2001 From: Lorry Tar Creator Date: Tue, 27 Jun 2017 06:07:23 +0000 Subject: webkitgtk-2.16.5 --- .../WebCore/editing/AlternativeTextController.cpp | 227 ++- Source/WebCore/editing/AlternativeTextController.h | 43 +- Source/WebCore/editing/AppendNodeCommand.cpp | 37 +- Source/WebCore/editing/AppendNodeCommand.h | 23 +- .../WebCore/editing/ApplyBlockElementCommand.cpp | 94 +- Source/WebCore/editing/ApplyBlockElementCommand.h | 15 +- Source/WebCore/editing/ApplyStyleCommand.cpp | 635 ++++--- Source/WebCore/editing/ApplyStyleCommand.h | 42 +- Source/WebCore/editing/BreakBlockquoteCommand.cpp | 48 +- Source/WebCore/editing/BreakBlockquoteCommand.h | 15 +- Source/WebCore/editing/ClipboardAccessPolicy.h | 36 + Source/WebCore/editing/CompositeEditCommand.cpp | 714 +++++--- Source/WebCore/editing/CompositeEditCommand.h | 84 +- Source/WebCore/editing/CreateLinkCommand.cpp | 18 +- Source/WebCore/editing/CreateLinkCommand.h | 19 +- Source/WebCore/editing/DeleteButton.cpp | 63 - Source/WebCore/editing/DeleteButton.h | 49 - Source/WebCore/editing/DeleteButtonController.cpp | 396 ----- Source/WebCore/editing/DeleteButtonController.h | 103 -- .../WebCore/editing/DeleteFromTextNodeCommand.cpp | 35 +- Source/WebCore/editing/DeleteFromTextNodeCommand.h | 26 +- Source/WebCore/editing/DeleteSelectionCommand.cpp | 288 ++-- Source/WebCore/editing/DeleteSelectionCommand.h | 33 +- Source/WebCore/editing/DictationAlternative.h | 11 +- Source/WebCore/editing/DictationCommand.cpp | 32 +- Source/WebCore/editing/DictationCommand.h | 18 +- Source/WebCore/editing/DictionaryPopupInfo.h | 46 + Source/WebCore/editing/EditAction.h | 38 +- Source/WebCore/editing/EditCommand.cpp | 153 +- Source/WebCore/editing/EditCommand.h | 37 +- Source/WebCore/editing/EditingAllInOne.cpp | 87 + Source/WebCore/editing/EditingBehavior.h | 26 +- Source/WebCore/editing/EditingBehaviorTypes.h | 8 +- Source/WebCore/editing/EditingBoundary.h | 11 +- Source/WebCore/editing/EditingStyle.cpp | 918 ++++++---- Source/WebCore/editing/EditingStyle.h | 123 +- Source/WebCore/editing/Editor.cpp | 1775 +++++++++++--------- Source/WebCore/editing/Editor.h | 430 +++-- Source/WebCore/editing/EditorCommand.cpp | 369 ++-- Source/WebCore/editing/EditorDeleteAction.h | 14 +- Source/WebCore/editing/EditorInsertAction.h | 22 +- Source/WebCore/editing/FindOptions.h | 12 +- Source/WebCore/editing/FormatBlockCommand.cpp | 84 +- Source/WebCore/editing/FormatBlockCommand.h | 21 +- Source/WebCore/editing/FrameSelection.cpp | 1081 +++++++----- Source/WebCore/editing/FrameSelection.h | 205 ++- Source/WebCore/editing/HTMLInterchange.cpp | 9 +- Source/WebCore/editing/HTMLInterchange.h | 11 +- Source/WebCore/editing/IndentOutdentCommand.cpp | 69 +- Source/WebCore/editing/IndentOutdentCommand.h | 24 +- .../WebCore/editing/InsertIntoTextNodeCommand.cpp | 43 +- Source/WebCore/editing/InsertIntoTextNodeCommand.h | 33 +- Source/WebCore/editing/InsertLineBreakCommand.cpp | 80 +- Source/WebCore/editing/InsertLineBreakCommand.h | 17 +- Source/WebCore/editing/InsertListCommand.cpp | 109 +- Source/WebCore/editing/InsertListCommand.h | 30 +- Source/WebCore/editing/InsertNodeBeforeCommand.cpp | 30 +- Source/WebCore/editing/InsertNodeBeforeCommand.h | 28 +- .../editing/InsertParagraphSeparatorCommand.cpp | 64 +- .../editing/InsertParagraphSeparatorCommand.h | 23 +- Source/WebCore/editing/InsertTextCommand.cpp | 50 +- Source/WebCore/editing/InsertTextCommand.h | 32 +- Source/WebCore/editing/MarkupAccumulator.cpp | 277 +-- Source/WebCore/editing/MarkupAccumulator.h | 45 +- .../editing/MergeIdenticalElementsCommand.cpp | 33 +- .../editing/MergeIdenticalElementsCommand.h | 19 +- .../WebCore/editing/ModifySelectionListLevel.cpp | 33 +- Source/WebCore/editing/ModifySelectionListLevel.h | 25 +- Source/WebCore/editing/MoveSelectionCommand.cpp | 29 +- Source/WebCore/editing/MoveSelectionCommand.h | 18 +- .../WebCore/editing/RemoveCSSPropertyCommand.cpp | 9 +- Source/WebCore/editing/RemoveCSSPropertyCommand.h | 19 +- Source/WebCore/editing/RemoveFormatCommand.cpp | 61 +- Source/WebCore/editing/RemoveFormatCommand.h | 17 +- Source/WebCore/editing/RemoveNodeCommand.cpp | 28 +- Source/WebCore/editing/RemoveNodeCommand.h | 21 +- .../RemoveNodePreservingChildrenCommand.cpp | 12 +- .../editing/RemoveNodePreservingChildrenCommand.h | 17 +- Source/WebCore/editing/RenderedPosition.cpp | 12 +- Source/WebCore/editing/RenderedPosition.h | 17 +- .../WebCore/editing/ReplaceNodeWithSpanCommand.cpp | 35 +- .../WebCore/editing/ReplaceNodeWithSpanCommand.h | 15 +- .../editing/ReplaceRangeWithTextCommand.cpp | 96 ++ .../WebCore/editing/ReplaceRangeWithTextCommand.h | 55 + Source/WebCore/editing/ReplaceSelectionCommand.cpp | 533 +++--- Source/WebCore/editing/ReplaceSelectionCommand.h | 46 +- Source/WebCore/editing/SelectionRectGatherer.cpp | 89 + Source/WebCore/editing/SelectionRectGatherer.h | 75 + Source/WebCore/editing/SetNodeAttributeCommand.cpp | 6 +- Source/WebCore/editing/SetNodeAttributeCommand.h | 19 +- Source/WebCore/editing/SetSelectionCommand.cpp | 8 +- Source/WebCore/editing/SetSelectionCommand.h | 19 +- Source/WebCore/editing/SimplifyMarkupCommand.cpp | 19 +- Source/WebCore/editing/SimplifyMarkupCommand.h | 15 +- Source/WebCore/editing/SmartReplace.cpp | 11 +- Source/WebCore/editing/SmartReplace.h | 11 +- Source/WebCore/editing/SmartReplaceCF.cpp | 73 + Source/WebCore/editing/SpellChecker.cpp | 57 +- Source/WebCore/editing/SpellChecker.h | 23 +- .../WebCore/editing/SpellingCorrectionCommand.cpp | 59 +- Source/WebCore/editing/SpellingCorrectionCommand.h | 23 +- Source/WebCore/editing/SplitElementCommand.cpp | 40 +- Source/WebCore/editing/SplitElementCommand.h | 21 +- Source/WebCore/editing/SplitTextNodeCommand.cpp | 23 +- Source/WebCore/editing/SplitTextNodeCommand.h | 21 +- .../SplitTextNodeContainingElementCommand.cpp | 10 +- .../SplitTextNodeContainingElementCommand.h | 15 +- Source/WebCore/editing/SurroundingText.h | 55 + Source/WebCore/editing/TextAffinity.h | 37 +- Source/WebCore/editing/TextCheckingHelper.cpp | 359 ++-- Source/WebCore/editing/TextCheckingHelper.h | 27 +- Source/WebCore/editing/TextGranularity.h | 15 +- .../WebCore/editing/TextInsertionBaseCommand.cpp | 24 +- Source/WebCore/editing/TextInsertionBaseCommand.h | 11 +- Source/WebCore/editing/TextIterator.cpp | 1702 ++++++++++--------- Source/WebCore/editing/TextIterator.h | 381 ++--- Source/WebCore/editing/TextIteratorBehavior.h | 66 + Source/WebCore/editing/TypingCommand.cpp | 352 +++- Source/WebCore/editing/TypingCommand.h | 73 +- Source/WebCore/editing/UndoStep.h | 7 +- Source/WebCore/editing/UnlinkCommand.cpp | 6 +- Source/WebCore/editing/UnlinkCommand.h | 17 +- Source/WebCore/editing/VisiblePosition.cpp | 193 ++- Source/WebCore/editing/VisiblePosition.h | 55 +- Source/WebCore/editing/VisibleSelection.cpp | 121 +- Source/WebCore/editing/VisibleSelection.h | 45 +- Source/WebCore/editing/VisibleUnits.cpp | 902 +++++----- Source/WebCore/editing/VisibleUnits.h | 118 +- .../editing/WrapContentsInDummySpanCommand.cpp | 40 +- .../editing/WrapContentsInDummySpanCommand.h | 25 +- Source/WebCore/editing/WritingDirection.h | 4 +- Source/WebCore/editing/atk/FrameSelectionAtk.cpp | 40 +- Source/WebCore/editing/gtk/EditorGtk.cpp | 151 ++ Source/WebCore/editing/htmlediting.cpp | 1116 ++++++------ Source/WebCore/editing/htmlediting.h | 248 ++- Source/WebCore/editing/markup.cpp | 483 +++--- Source/WebCore/editing/markup.h | 38 +- 137 files changed, 9987 insertions(+), 8049 deletions(-) create mode 100644 Source/WebCore/editing/ClipboardAccessPolicy.h delete mode 100644 Source/WebCore/editing/DeleteButton.cpp delete mode 100644 Source/WebCore/editing/DeleteButton.h delete mode 100644 Source/WebCore/editing/DeleteButtonController.cpp delete mode 100644 Source/WebCore/editing/DeleteButtonController.h create mode 100644 Source/WebCore/editing/DictionaryPopupInfo.h create mode 100644 Source/WebCore/editing/EditingAllInOne.cpp create mode 100644 Source/WebCore/editing/ReplaceRangeWithTextCommand.cpp create mode 100644 Source/WebCore/editing/ReplaceRangeWithTextCommand.h create mode 100644 Source/WebCore/editing/SelectionRectGatherer.cpp create mode 100644 Source/WebCore/editing/SelectionRectGatherer.h create mode 100644 Source/WebCore/editing/SmartReplaceCF.cpp create mode 100644 Source/WebCore/editing/SurroundingText.h create mode 100644 Source/WebCore/editing/TextIteratorBehavior.h create mode 100644 Source/WebCore/editing/gtk/EditorGtk.cpp (limited to 'Source/WebCore/editing') 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 namespace WebCore { class AutocorrectionAlternativeDetails : public AlternativeTextDetails { public: - static PassRefPtr create(const String& replacementString) + static Ref 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 create(uint64_t dictationContext) + static Ref 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& markerTypesForAutocorrection() { - DEFINE_STATIC_LOCAL(Vector, markerTypesForAutoCorrection, ()); - if (markerTypesForAutoCorrection.isEmpty()) { - markerTypesForAutoCorrection.append(DocumentMarker::Replacement); - markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator); - markerTypesForAutoCorrection.append(DocumentMarker::SpellCheckingExemption); - markerTypesForAutoCorrection.append(DocumentMarker::Autocorrected); + static NeverDestroyed> 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& markerTypesForReplacement() { - DEFINE_STATIC_LOCAL(Vector, markerTypesForReplacement, ()); - if (markerTypesForReplacement.isEmpty()) { - markerTypesForReplacement.append(DocumentMarker::Replacement); - markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption); + static NeverDestroyed> markerTypesForReplacement; + if (markerTypesForReplacement.get().isEmpty()) { + markerTypesForReplacement.get().append(DocumentMarker::Replacement); + markerTypesForReplacement.get().append(DocumentMarker::SpellCheckingExemption); } return markerTypesForReplacement; } static const Vector& markerTypesForAppliedDictationAlternative() { - DEFINE_STATIC_LOCAL(Vector, markerTypesForAppliedDictationAlternative, ()); - if (markerTypesForAppliedDictationAlternative.isEmpty()) - markerTypesForAppliedDictationAlternative.append(DocumentMarker::SpellCheckingExemption); + static NeverDestroyed> markerTypesForAppliedDictationAlternative; + if (markerTypesForAppliedDictationAlternative.get().isEmpty()) + markerTypesForAppliedDictationAlternative.get().append(DocumentMarker::SpellCheckingExemption); return markerTypesForAppliedDictationAlternative; } -static bool markersHaveIdenticalDescription(const Vector& markers) +static bool markersHaveIdenticalDescription(const Vector& markers) { if (markers.isEmpty()) return true; @@ -126,7 +128,7 @@ static bool markersHaveIdenticalDescription(const Vector& 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 paragraphRangeContainingCorrection = range->cloneRange(ec); - if (ec) - return; + RefPtr 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 correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition()); - if (ec) - return; + RefPtr 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 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 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 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 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::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&) +void AlternativeTextController::timerFired() { m_isDismissedByEditing = false; switch (m_alternativeTextInfo.type) { @@ -336,13 +340,13 @@ void AlternativeTextController::timerFired(Timer&) } break; case AlternativeTextTypeReversion: { - if (!m_alternativeTextInfo.rangeWithAlternative) + auto* details = static_cast(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(m_alternativeTextInfo.details.get()); if (AlternativeTextClient* client = alternativeTextClient()) client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, details->replacementString(), Vector()); } @@ -353,9 +357,9 @@ void AlternativeTextController::timerFired(Timer&) break; String paragraphText = plainText(TextCheckingParagraph(m_alternativeTextInfo.rangeWithAlternative).paragraphRange().get()); Vector 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 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 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 replacementRange) { - if (AlternativeTextClient* client = alternativeTextClient()) - client->recordAutocorrectionResponse(AutocorrectionReverted, replacedString, replacementString); -} - -void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr replacementRange) -{ - recordAutocorrectionResponseReversed(replacedString, plainText(replacementRange.get())); + if (auto client = alternativeTextClient()) + client->recordAutocorrectionResponse(response, replacedString, plainText(replacementRange.get())); } void AlternativeTextController::markReversed(PassRefPtr 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 replacedRange, const String& replacedString) { - Vector 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 correctedOnceMarkers = markers.markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected); + DocumentMarkerController& markers = rangeOfCorrection->startContainer().document().markers(); + Vector 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 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 precedingCharacterRange = Range::create(*m_frame.document(), precedingCharacterPosition, beginningOfRange); - Vector markers = markerController.markersInRange(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection); - - for (size_t i = 0; i < markers.size(); ++i) { - if (markers[i]->description() == stringToBeReplaced) + Vector 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(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(marker.details()); - if (!markerDetails) + if (!WTF::holds_alternative(marker.data())) return false; - if (currentWord != markerDetails->originalText()) + auto& markerData = WTF::get(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 event = TextEvent::createForDictation(m_frame.document()->domWindow(), text, dictationAlternatives); + Ref 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(marker->details()); - if (AlternativeTextClient* client = alternativeTextClient()) - client->removeDictationAlternatives(details->dictationContext()); + ASSERT(WTF::holds_alternative(marker.data())); + if (auto* client = alternativeTextClient()) + client->removeDictationAlternatives(WTF::get(marker.data()).context); #else UNUSED_PARAM(marker); #endif } -Vector AlternativeTextController::dictationAlternativesForMarker(const DocumentMarker* marker) +Vector AlternativeTextController::dictationAlternativesForMarker(const DocumentMarker& marker) { #if USE(DICTATION_ALTERNATIVES) - ASSERT(marker->type() == DocumentMarker::DictationAlternatives); - if (AlternativeTextClient* client = alternativeTextClient()) { - DictationMarkerDetails* details = static_cast(marker->details()); - return client->dictationAlternatives(details->dictationContext()); - } + ASSERT(marker.type() == DocumentMarker::DictationAlternatives); + if (auto* client = alternativeTextClient()) + return client->dictationAlternatives(WTF::get(marker.data()).context); return Vector(); #else UNUSED_PARAM(marker); @@ -729,12 +720,12 @@ void AlternativeTextController::applyDictationAlternative(const String& alternat #if USE(DICTATION_ALTERNATIVES) Editor& editor = m_frame.editor(); RefPtr 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 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 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 { public: - AlternativeTextDetails() { } virtual ~AlternativeTextDetails() { } }; @@ -60,24 +58,6 @@ struct AlternativeTextInfo { RefPtr details; }; -class DictationMarkerDetails : public DocumentMarkerDetails { -public: - static PassRefPtr 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 replacementRange) UNLESS_ENABLED({ UNUSED_PARAM(replacedString); UNUSED_PARAM(replacementRange); }) + void recordAutocorrectionResponse(AutocorrectionResponse, const String& replacedString, PassRefPtr replacementRange) UNLESS_ENABLED({ UNUSED_PARAM(replacedString); UNUSED_PARAM(replacementRange); }) void markReversed(PassRefPtr changedRange) UNLESS_ENABLED({ UNUSED_PARAM(changedRange); }) void markCorrection(PassRefPtr 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&, Event*); - void removeDictationAlternativesForMarker(const DocumentMarker*); - Vector dictationAlternativesForMarker(const DocumentMarker*); + void removeDictationAlternativesForMarker(const DocumentMarker&); + Vector 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&); - void timerFired(Timer&); - 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 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 parent, PassRefPtr node) - : SimpleEditCommand(parent->document()) +AppendNodeCommand::AppendNodeCommand(PassRefPtr parent, Ref&& 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& 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 create(PassRefPtr parent, PassRefPtr node) + static Ref create(PassRefPtr parent, Ref&& node, EditAction editingAction) { - return adoptRef(new AppendNodeCommand(parent, node)); + return adoptRef(*new AppendNodeCommand(parent, WTFMove(node), editingAction)); } private: - AppendNodeCommand(PassRefPtr parent, PassRefPtr); + AppendNodeCommand(PassRefPtr parent, Ref&&, EditAction); - virtual void doApply() override; - virtual void doUnapply() override; + void doApply() override; + void doUnapply() override; #ifndef NDEBUG - virtual void getNodesInCommand(HashSet&) override; + void getNodesInCommand(HashSet&) override; #endif RefPtr m_parent; - RefPtr m_node; + Ref 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 blockquote = createBlockElement(); - insertNodeAt(blockquote, start); - RefPtr 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(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(*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 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->previousSibling())) { ASSERT(start.offsetInContainerNode() < position.offsetInContainerNode()); - start = Position(toText(text->previousSibling()), start.offsetInContainerNode()); + start = Position(downcast(text->previousSibling()), start.offsetInContainerNode()); } - if (text == end.containerNode() && text->previousSibling() && text->previousSibling()->isTextNode()) { + if (text == end.containerNode() && is(text->previousSibling())) { ASSERT(end.offsetInContainerNode() < position.offsetInContainerNode()); - end = Position(toText(text->previousSibling()), end.offsetInContainerNode()); + end = Position(downcast(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(m_endOfLastParagraph.offsetInContainerNode()) <= toText(text->previousSibling())->length()) - m_endOfLastParagraph = Position(toText(text->previousSibling()), m_endOfLastParagraph.offsetInContainerNode()); + if (is(*text->previousSibling()) + && static_cast(m_endOfLastParagraph.offsetInContainerNode()) <= downcast(text->previousSibling())->length()) + m_endOfLastParagraph = Position(downcast(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 ApplyBlockElementCommand::createBlockElement() +Ref ApplyBlockElementCommand::createBlockElement() { - RefPtr 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 createBlockElement(); + Ref 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&) = 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 #include #include @@ -55,22 +59,21 @@ using namespace HTMLNames; static int toIdentifier(PassRefPtr value) { - return (value && value->isPrimitiveValue()) ? static_pointer_cast(value)->getValueID() : 0; + return (value && value->isPrimitiveValue()) ? static_pointer_cast(value)->valueID() : 0; } static String& styleSpanClassString() { - DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass))); + static NeverDestroyed styleSpanClassString(AppleStyleSpanClass); return styleSpanClassString; } -bool isLegacyAppleStyleSpan(const Node *node) +bool isLegacyAppleStyleSpan(const Node* node) { - if (!node || !node->isHTMLElement()) + if (!is(node)) return false; - const HTMLElement* elem = toHTMLElement(node); - return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString(); + return downcast(*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(element)) return false; - return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), AllowNonEmptyStyleAttribute); + return hasNoAttributeOrOnlyStyleAttribute(downcast(element), AllowNonEmptyStyleAttribute); } static inline bool isSpanWithoutAttributesOrUnstyledStyleSpan(const Element* element) { - if (!element || !element->isHTMLElement() || !element->hasTagName(spanTag)) + if (!is(element)) return false; - return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), StyleAttributeShouldBeEmpty); + return hasNoAttributeOrOnlyStyleAttribute(downcast(element), StyleAttributeShouldBeEmpty); } bool isEmptyFontTag(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty) { - if (!element || !element->hasTagName(fontTag)) + if (!is(element)) return false; - return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), shouldStyleAttributeBeEmpty); + return hasNoAttributeOrOnlyStyleAttribute(downcast(element), shouldStyleAttributeBeEmpty); } -static PassRefPtr createFontElement(Document& document) +static RefPtr createFontElement(Document& document) { return createHTMLElement(document, fontTag); } -PassRefPtr createStyleSpanElement(Document& document) +RefPtr 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, 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 block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode()); if (!m_removeOnly) { RefPtr newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent()); if (newBlock) block = newBlock; } - ASSERT(!block || block->isHTMLElement()); - if (block && block->isHTMLElement()) { - removeCSSStyle(style, toHTMLElement(block.get())); + ASSERT(!block || is(*block)); + if (is(block.get())) { + removeCSSStyle(style, downcast(block.get())); if (!m_removeOnly) - addBlockStyle(styleChange, toHTMLElement(block.get())); + addBlockStyle(styleChange, downcast(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(*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(*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(*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 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> 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 element; - if (node->isHTMLElement()) { + if (is(*node)) { // Only work on fully selected nodes. - if (!nodeFullySelected(node, start, end)) + if (!nodeFullySelected(downcast(*node), start, end)) continue; - element = toHTMLElement(node); - } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) { + element = &downcast(*node); + } else if (is(*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 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(*node) || !isStyleSpanOrSpanWithOnlyStyleAttribute(downcast(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(*highestAncestorWithUnicodeBidi) && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::AllProperties)->textDirection(highestAncestorDirection) && highestAncestorDirection == allowedDirection) { if (!nextHighestAncestorWithUnicodeBidi) - return toHTMLElement(highestAncestorWithUnicodeBidi); + return downcast(highestAncestorWithUnicodeBidi); - unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi); + unsplitAncestor = downcast(highestAncestorWithUnicodeBidi); highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi; } // Split every ancestor through highest ancestor with embedding. RefPtr currentNode = node; while (currentNode) { - RefPtr parent = toElement(currentNode->parentNode()); + RefPtr parent = downcast(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(*ancestor)) continue; - StyledElement* element = toStyledElement(n); - int unicodeBidi = toIdentifier(ComputedStyleExtractor(element).propertyValue(CSSPropertyUnicodeBidi)); + StyledElement& element = downcast(*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 inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle()); + RefPtr 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 startDummySpanAncestor = 0; - RefPtr endDummySpanAncestor = 0; + RefPtr startDummySpanAncestor; + RefPtr 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 hello, we need to include the font element in our run // to generate hello instead of hello - RefPtr 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 start; @@ -766,35 +776,35 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, PassRef Vector runs; RefPtr node = startNode; for (RefPtr 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(*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 inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle()); + HTMLElement& element = downcast(*node); + RefPtr 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 next = runStart; - for (RefPtr node = next; node && node->inDocument() && node != pastEndNode; node = next) { - if (editingIgnoresContent(node.get())) { + for (RefPtr 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(*node)) continue; RefPtr previousSibling = node->previousSibling(); RefPtr nextSibling = node->nextSibling(); RefPtr parent = node->parentNode(); - removeInlineStyleFromElement(style, toHTMLElement(node.get()), RemoveAlways); - if (!node->inDocument()) { + removeInlineStyleFromElement(style, downcast(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 properties; - if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, properties)) + RefPtr 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 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(*ancestor) && shouldRemoveInlineStyleFromElement(style, downcast(ancestor))) + result = downcast(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(*node)) return; RefPtr newInlineStyle = style; - if (node->isHTMLElement() && toHTMLElement(node)->inlineStyle()) { + if (is(*node) && downcast(node)->inlineStyle()) { newInlineStyle = style->copy(); - newInlineStyle->mergeInlineStyleOfElement(toHTMLElement(node), EditingStyle::OverrideValues); + newInlineStyle->mergeInlineStyleOfElement(downcast(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(*node)) { + setNodeAttribute(downcast(node), styleAttr, newInlineStyle->style()->asText()); return; } @@ -1051,31 +1057,31 @@ void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* getChildNodes(*current.get(), currentChildren); RefPtr styledElement; - if (current->isStyledElement() && isStyledInlineElementToRemove(toElement(current.get()))) { - styledElement = toStyledElement(current.get()); + if (is(*current) && isStyledInlineElementToRemove(downcast(current.get()))) { + styledElement = downcast(current.get()); elementsToPushDown.append(*styledElement); } RefPtr styleToPushDown = EditingStyle::create(); - if (current->isHTMLElement()) - removeInlineStyleFromElement(style, toHTMLElement(current.get()), RemoveIfNeeded, styleToPushDown.get()); + if (is(*current)) + removeInlineStyleFromElement(style, downcast(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& 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 wrapper = elementsToPushDown[i]->cloneElementWithoutChildren(); + for (auto& element : elementsToPushDown) { + RefPtr 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 hello
world
, we want Position("world", 0) instead. - Node* pushDownStartContainer = pushDownStart.containerNode(); - if (pushDownStartContainer && pushDownStartContainer->isTextNode() - && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset()) + auto* pushDownStartContainer = pushDownStart.containerNode(); + if (is(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(pushDownEndContainer) && !pushDownEnd.computeOffsetInContainerNode()) pushDownEnd = previousVisuallyDistinctCandidate(pushDownEnd); pushDownInlineStyleAroundNode(style, pushDownStart.deprecatedNode()); @@ -1125,32 +1130,32 @@ void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &s RefPtr node = start.deprecatedNode(); while (node) { RefPtr 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 elem = toHTMLElement(node.get()); - RefPtr prev = NodeTraversal::previousPostOrder(elem.get()); - RefPtr next = NodeTraversal::next(elem.get()); + if (is(*node) && nodeFullySelected(downcast(*node), start, end)) { + Ref element = downcast(*node); + RefPtr prev = NodeTraversal::previousPostOrder(element); + RefPtr next = NodeTraversal::next(element); RefPtr styleToPushDown; RefPtr 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(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(end.containerNode())); bool shouldUpdateStart = start.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode(); - Text* text = toText(end.deprecatedNode()); - splitTextNode(text, end.offsetInContainerNode()); + Text& text = downcast(*end.deprecatedNode()); + splitTextNode(&text, end.offsetInContainerNode()); - Node* prevNode = text->previousSibling(); - if (!prevNode || !prevNode->isTextNode()) + Node* prevNode = text.previousSibling(); + if (!is(prevNode)) return; - Position newStart = shouldUpdateStart ? Position(toText(prevNode), start.offsetInContainerNode()) : start; + Position newStart = shouldUpdateStart ? Position(downcast(prevNode), start.offsetInContainerNode()) : start; updateStartEnd(newStart, lastPositionInNode(prevNode)); } void ApplyStyleCommand::splitTextElementAtStart(const Position& start, const Position& end) { - ASSERT(start.containerNode()->isTextNode()); + ASSERT(is(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(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(firstTextNode)) return; - Position newStart = shouldUpdateStart ? Position(toText(firstTextNode), start.offsetInContainerNode()) : start; + Position newStart = shouldUpdateStart ? Position(downcast(firstTextNode), start.offsetInContainerNode()) : start; updateStartEnd(newStart, positionAfterNode(firstTextNode)); } bool ApplyStyleCommand::shouldSplitTextElement(Element* element, EditingStyle* style) { - if (!element || !element->isHTMLElement()) + if (!is(element)) return false; - return shouldRemoveInlineStyleFromElement(style, toHTMLElement(element)); + return shouldRemoveInlineStyleFromElement(style, &downcast(*element)); } bool ApplyStyleCommand::isValidCaretPositionInTextNode(const Position& position) { Node* node = position.containerNode(); - if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node->isTextNode()) + if (position.anchorType() != Position::PositionIsOffsetInAnchor || !is(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(*previousSibling); + auto& element = downcast(*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(*nextSibling); + auto& element = downcast(*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 passedStartNode, PassRefPtr endNode, PassRefPtr elementToInsert) @@ -1360,7 +1353,7 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr passedStar RefPtr node = startNode; while (node) { RefPtr next = node->nextSibling(); - if (node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) { + if (isEditableNode(*node)) { removeNode(node); appendNode(node, element); } @@ -1371,15 +1364,15 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr passedStar RefPtr nextSibling = element->nextSibling(); RefPtr 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(nextSibling.get())); + + if (is(previousSibling.get()) && previousSibling->hasEditableStyle()) { + auto* mergedElement = previousSibling->nextSibling(); + ASSERT(mergedElement); + if (mergedElement->hasEditableStyle() && areIdenticalElements(*previousSibling, *mergedElement)) + mergeIdenticalElements(downcast(previousSibling.get()), downcast(mergedElement)); } // FIXME: We should probably call updateStartEnd if the start or end was in the node @@ -1389,12 +1382,13 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr passedStar void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block) { + ASSERT(styleChange.cssStyle()); // Do not check for legacy styles here. Those styles, like and , 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 passedStart, PassRefPtr passedEnd, EAddStyledElement addStyledElement) { - if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd->inDocument()) + if (!passedStart || !passedEnd || !passedStart->isConnected() || !passedEnd->isConnected()) return; RefPtr start = passedStart; @@ -1423,7 +1417,7 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtr startNode, RefPtr& 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(*startNode)) { dummyElement = createStyleSpanElement(document()); insertNodeAt(dummyElement, positionBeforeNode(startNode.get())); return firstPositionInOrBeforeNode(dummyElement.get()); @@ -1436,22 +1430,25 @@ void ApplyStyleCommand::applyInlineStyleChange(PassRefPtr passedStart, Pas { RefPtr startNode = passedStart; RefPtr 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(*startNode)) { + auto& container = downcast(*startNode); + if (is(container)) + fontContainer = &downcast(container); + if (is(container) || (!is(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 passedStart, Pas if (styleChange.applyFontSize()) setNodeAttribute(fontContainer, sizeAttr, styleChange.fontSize()); } else { - RefPtr 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 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 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 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(*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> 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(next)) continue; - Text* nextText = toText(next); + Text& nextText = downcast(*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 create(Document& document, const EditingStyle* style, EditAction action = EditActionChangeAttributes, EPropertyLevel level = PropertyDefault) + static Ref 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 create(Document& document, const EditingStyle* style, const Position& start, const Position& end, EditAction action = EditActionChangeAttributes, EPropertyLevel level = PropertyDefault) + static Ref 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 create(PassRefPtr element, bool removeOnly = false, EditAction action = EditActionChangeAttributes) + static Ref create(PassRefPtr element, bool removeOnly = false, EditAction action = EditActionChangeAttributes) { - return adoptRef(new ApplyStyleCommand(element, removeOnly, action)); + return adoptRef(*new ApplyStyleCommand(element, removeOnly, action)); } - static PassRefPtr create(Document& document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction action = EditActionChangeAttributes) + static Ref 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, 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& runStart, RefPtr& runEnd, PassRefPtr pastEndNode); - bool removeInlineStyleFromElement(EditingStyle*, PassRefPtr, InlineStyleRemovalMode = RemoveIfNeeded, EditingStyle* extractedStyle = 0); + bool removeInlineStyleFromElement(EditingStyle*, PassRefPtr, 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 m_style; - EditAction m_editingAction; EPropertyLevel m_propertyLevel; Position m_start; Position m_end; bool m_useEndingSelection; RefPtr 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 createStyleSpanElement(Document&); +RefPtr 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 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(*startNode)) { + Text& textNode = downcast(*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 clonedBlockquote = toElement(topBlockquote)->cloneElementWithoutChildren(); - insertNodeAfter(clonedBlockquote.get(), breakNode.get()); + RefPtr clonedBlockquote = downcast(*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 clonedAncestor = clonedBlockquote; for (size_t i = ancestors.size(); i != 0; --i) { - RefPtr clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(); + RefPtr 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(listChildNode->renderer())) + setNodeAttribute(clonedChild, startAttr, AtomicString::number(downcast(*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 create(Document& document) + static Ref 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::create(Document& document, +int AccessibilityUndoReplacedText::indexForVisiblePosition(const VisiblePosition& position, RefPtr& 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::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 = 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 . // 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 = 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 . // 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& 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 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> CompositeEditCommand::targetRanges() const +{ + ASSERT(!isEditingTextAreaOrTextInput()); + auto firstRange = frame().selection().selection().firstRange(); + if (!firstRange) + return { }; + + RefPtr range = StaticRange::createFromRange(*firstRange); + return { 1, range }; +} + +Vector> CompositeEditCommand::targetRangesForBindings() const { - CompositeEditCommand* command = this; - while (command && command->parent()) - command = command->parent(); + if (isEditingTextAreaOrTextInput()) + return { }; + + return targetRanges(); +} + +RefPtr 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 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 command, const VisibleSelection& selection) @@ -316,7 +503,7 @@ void CompositeEditCommand::removeStyledElement(PassRefPtr 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(*node)) return false; Node* parentNode = node->parentNode(); if (parentNode && parentNode->firstChild() != parentNode->lastChild()) return false; - if (!toElement(node)->hasAttributes()) + if (!downcast(*node).hasAttributes()) return true; return false; @@ -341,17 +529,17 @@ bool CompositeEditCommand::isRemovableBlock(const Node* node) void CompositeEditCommand::insertNodeBefore(PassRefPtr insertChild, PassRefPtr refChild, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) { - ASSERT(!refChild->hasTagName(bodyTag)); - applyCommandToComposite(InsertNodeBeforeCommand::create(insertChild, refChild, shouldAssumeContentIsAlwaysEditable)); + applyCommandToComposite(InsertNodeBeforeCommand::create(insertChild, refChild, shouldAssumeContentIsAlwaysEditable, editingAction())); } void CompositeEditCommand::insertNodeAfter(PassRefPtr insertChild, PassRefPtr 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 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(refChild)); + } else if (caretMinOffset(*refChild) >= offset) insertNodeBefore(insertChild, refChild); - else if (refChild->isTextNode() && caretMaxOffset(refChild) > offset) { - splitTextNode(toText(refChild), offset); + else if (is(*refChild) && caretMaxOffset(*refChild) > offset) { + splitTextNode(downcast(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 insertChild, const Posi void CompositeEditCommand::appendNode(PassRefPtr node, PassRefPtr 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, unsigned from, unsigned to) { Vector> 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, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) { if (!node || !node->nonShadowBoundaryParentNode()) return; - applyCommandToComposite(RemoveNodeCommand::create(node, shouldAssumeContentIsAlwaysEditable)); + applyCommandToComposite(RemoveNodeCommand::create(*node, shouldAssumeContentIsAlwaysEditable, editingAction())); } void CompositeEditCommand::removeNodePreservingChildren(PassRefPtr node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) { - applyCommandToComposite(RemoveNodePreservingChildrenCommand::create(node, shouldAssumeContentIsAlwaysEditable)); + applyCommandToComposite(RemoveNodePreservingChildrenCommand::create(node, shouldAssumeContentIsAlwaysEditable, editingAction())); } void CompositeEditCommand::removeNodeAndPruneAncestors(PassRefPtr node) { RefPtr parent = node->parentNode(); removeNode(node); - prune(parent.release()); + prune(WTFMove(parent)); } void CompositeEditCommand::moveRemainingSiblingsToNewParent(Node* node, Node* pastLastNodeToMove, PassRefPtr 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) { if (RefPtr highestNodeToRemove = highestNodeToRemoveInPruning(node.get())) - removeNode(highestNodeToRemove.release()); + removeNode(WTFMove(highestNodeToRemove)); } void CompositeEditCommand::splitTextNode(PassRefPtr node, unsigned offset) @@ -492,7 +680,7 @@ void CompositeEditCommand::mergeIdenticalElements(PassRefPtr prpFirst, applyCommandToComposite(MergeIdenticalElementsCommand::create(first, second)); } -void CompositeEditCommand::wrapContentsInDummySpan(PassRefPtr element) +void CompositeEditCommand::wrapContentsInDummySpan(Element& element) { applyCommandToComposite(WrapContentsInDummySpanCommand::create(element)); } @@ -502,7 +690,6 @@ void CompositeEditCommand::splitTextNodeContainingElement(PassRefPtr 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 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 node, unsigned offset, unsigned count) { - applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count)); + applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count, editingAction())); } void CompositeEditCommand::replaceTextInNode(PassRefPtr prpNode, unsigned offset, unsigned count, const String& replacementText) { RefPtr 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 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& markerPointers, Vector& markers) +static Vector copyMarkers(const Vector& markerPointers) { - size_t arraySize = markerPointers.size(); - markers.reserveCapacity(arraySize); - for (size_t i = 0; i < arraySize; ++i) - markers.append(*markerPointers[i]); + Vector markers; + markers.reserveInitialCapacity(markerPointers.size()); + for (auto& markerPointer : markerPointers) + markers.uncheckedAppend(*markerPointer); + + return markers; } void CompositeEditCommand::replaceTextInNodePreservingMarkers(PassRefPtr prpNode, unsigned offset, unsigned count, const String& replacementText) { RefPtr node(prpNode); DocumentMarkerController& markerController = document().markers(); - Vector 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 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(*position.containerNode()), position.offsetInContainerNode()); return positionInParentBeforeNode(tabSpan); } @@ -637,10 +824,20 @@ void CompositeEditCommand::insertNodeAtTabSpanPosition(PassRefPtr 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 element, void CompositeEditCommand::removeNodeAttribute(PassRefPtr element, const QualifiedName& attribute) { - setNodeAttribute(element, attribute, AtomicString()); + setNodeAttribute(element, attribute, nullAtom); } void CompositeEditCommand::setNodeAttribute(PassRefPtr element, const QualifiedName& attribute, const AtomicString& value) @@ -664,34 +861,33 @@ void CompositeEditCommand::setNodeAttribute(PassRefPtr 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(node)) return false; - Text* textNode = toText(node); - if (textNode->length() == 0) + Text& textNode = downcast(*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(*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(node), position.offsetInContainerNode(), position.offsetInContainerNode()); } void CompositeEditCommand::rebalanceWhitespaceOnTextSubstring(PassRefPtr prpTextNode, int startOffset, int endOffset) @@ -726,19 +922,19 @@ void CompositeEditCommand::rebalanceWhitespaceOnTextSubstring(PassRefPtr 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 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(node)) return; - Text* textNode = toText(node); + Text& textNode = downcast(*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(*previous.deprecatedNode()) && !is(*previous.deprecatedNode())) + replaceTextInNodePreservingMarkers(downcast(previous.deprecatedNode()), previous.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); + if (deprecatedIsCollapsibleWhitespace(visiblePos.characterAfter()) && is(*position.deprecatedNode()) && !is(*position.deprecatedNode())) + replaceTextInNodePreservingMarkers(downcast(position.deprecatedNode()), position.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); } void CompositeEditCommand::rebalanceWhitespace() @@ -824,7 +1020,7 @@ void CompositeEditCommand::deleteInsignificantText(PassRefPtr 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 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> 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(*node)) + nodes.append(downcast(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(textNode->length()); deleteInsignificantText(textNode, startOffset, endOffset); @@ -901,52 +1096,51 @@ void CompositeEditCommand::deleteInsignificantTextDownstream(const Position& pos deleteInsignificantText(pos, end); } -PassRefPtr CompositeEditCommand::appendBlockPlaceholder(PassRefPtr container) +RefPtr CompositeEditCommand::appendBlockPlaceholder(PassRefPtr container) { if (!container) - return 0; + return nullptr; document().updateLayoutIgnorePendingStylesheets(); // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964. ASSERT(container->renderer()); - RefPtr placeholder = createBlockPlaceholderElement(document()); - appendNode(placeholder, container); - return placeholder.release(); + auto placeholder = createBlockPlaceholderElement(document()); + appendNode(placeholder.ptr(), container); + return WTFMove(placeholder); } -PassRefPtr CompositeEditCommand::insertBlockPlaceholder(const Position& pos) +RefPtr 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 placeholder = createBlockPlaceholderElement(document()); - insertNodeAt(placeholder, pos); - return placeholder.release(); + auto placeholder = createBlockPlaceholderElement(document()); + insertNodeAt(placeholder.ptr(), pos); + return WTFMove(placeholder); } -PassRefPtr CompositeEditCommand::addBlockPlaceholderIfNeeded(Element* container) +RefPtr 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(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(*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(*p.anchorNode())) { removeNode(p.anchorNode()); return; } - deleteTextFromNode(toText(p.anchorNode()), p.offsetInContainerNode(), 1); + deleteTextFromNode(downcast(p.anchorNode()), p.offsetInContainerNode(), 1); } -PassRefPtr CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position) +Ref CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position) { - RefPtr 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 CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position& pos) +RefPtr CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position& pos) { if (pos.isNull()) - return 0; + return nullptr; document().updateLayoutIgnorePendingStylesheets(); @@ -994,7 +1188,7 @@ PassRefPtr 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 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(*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 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 child = item->cloneNode(isTableElement(item)); - appendNode(child, toElement(lastNode.get())); - lastNode = child.release(); + auto child = item->cloneNode(isRenderedTable(item)); + appendNode(child.ptr(), downcast(lastNode.get())); + lastNode = WTFMove(child); } } @@ -1092,7 +1289,7 @@ void CompositeEditCommand::cloneParagraphUnderNewElement(const Position& start, } RefPtr startNode = start.deprecatedNode(); - for (RefPtr node = NodeTraversal::nextSkippingChildren(startNode.get(), outerNode.get()); node; node = NodeTraversal::nextSkippingChildren(node.get(), outerNode.get())) { + for (RefPtr 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 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(*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(*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 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 CompositeEditCommand::shouldBreakOutOfEmptyListItem() const { - RefPtr emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart()); + auto emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart()); if (!emptyListItem) - return false; - - RefPtr style = EditingStyle::create(endingSelection().start()); - style->mergeTypingStyle(document()); + return std::nullopt; - RefPtr 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 newBlock = 0; + auto emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart()); + auto listNode = emptyListItem->parentNode(); + auto style = EditingStyle::create(endingSelection().start()); + style->mergeTypingStyle(document()); + + RefPtr newBlock; if (ContainerNode* blockEnclosingList = listNode->parentNode()) { - if (blockEnclosingList->hasTagName(liTag)) { // listNode is inside another list item - if (visiblePositionAfterNode(blockEnclosingList) == visiblePositionAfterNode(listNode.get())) { + if (is(*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.
  • hello

should become
  • hello

after this section // If listNode does NOT appear at the end, then we should consider it as a regular paragraph. // e.g.

    hello
should become

  • hello
at the end - splitElement(toElement(blockEnclosingList), listNode); + splitElement(downcast(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 previousListNode = emptyListItem->isElementNode() ? ElementTraversal::previousSibling(emptyListItem.get()): emptyListItem->previousSibling(); - RefPtr nextListNode = emptyListItem->isElementNode() ? ElementTraversal::nextSibling(emptyListItem.get()): emptyListItem->nextSibling(); - if (isListItem(nextListNode.get()) || isListElement(nextListNode.get())) { + RefPtr previousListNode = emptyListItem->isElementNode() ? ElementTraversal::previousSibling(*emptyListItem): emptyListItem->previousSibling(); + RefPtr 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(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 br = createBreakElement(document()); + RefPtr 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
...
, 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(*caretPos.deprecatedNode())) { ASSERT(caretPos.deprecatedEditingOffset() == 0); - Text* textNode = toText(caretPos.deprecatedNode()); - ContainerNode* parentNode = textNode->parentNode(); + Text& textNode = downcast(*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 CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool shouldSplitAncestor) +RefPtr CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool shouldSplitAncestor) { ASSERT(start); ASSERT(end); @@ -1528,21 +1740,21 @@ PassRefPtr CompositeEditCommand::splitTreeToNode(Node* start, Node* end, b RefPtr endNode = end; for (node = start; node && node->parentNode() != endNode; node = node->parentNode()) { - if (!node->parentNode()->isElementNode()) + if (!is(*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(node->parentNode()), node); } - return node.release(); + return node; } -PassRefPtr createBlockPlaceholderElement(Document& document) +Ref 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&) const; + String textDeletedByUnapply(); + String textDeletedByReapply(); + + String m_replacedText; + VisiblePositionIndexRange m_rangeDeletedByUnapply; + VisiblePositionIndexRange m_rangeDeletedByReapply; +}; + class EditCommandComposition : public UndoStep { public: - static PassRefPtr create(Document&, const VisibleSelection&, const VisibleSelection&, EditAction); + static Ref 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&); @@ -68,6 +94,7 @@ private: Vector> m_commands; RefPtr m_startingRootEditableElement; RefPtr 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> targetRangesForBindings() const; + virtual RefPtr 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> 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, unsigned offset, unsigned count); -#if PLATFORM(IOS) void inputText(const String&, bool selectInsertedText = false); -#endif bool isRemovableBlock(const Node*); void insertNodeAfter(PassRefPtr, PassRefPtr refChild); void insertNodeAt(PassRefPtr, const Position&); @@ -130,7 +167,7 @@ protected: void removeNodePreservingChildren(PassRefPtr, ShouldAssumeContentIsAlwaysEditable = DoNotAssumeContentIsAlwaysEditable); void removeNodeAndPruneAncestors(PassRefPtr); void moveRemainingSiblingsToNewParent(Node*, Node* pastLastNodeToMove, PassRefPtr prpNewParent); - void updatePositionForNodeRemovalPreservingChildren(Position&, Node*); + void updatePositionForNodeRemovalPreservingChildren(Position&, Node&); void prune(PassRefPtr); void replaceTextInNode(PassRefPtr, unsigned offset, unsigned count, const String& replacementText); Position replaceSelectedTextInNode(const String&); @@ -140,20 +177,20 @@ protected: void splitElement(PassRefPtr, PassRefPtr atChild); void splitTextNode(PassRefPtr, unsigned offset); void splitTextNodeContainingElement(PassRefPtr, unsigned offset); - void wrapContentsInDummySpan(PassRefPtr); + void wrapContentsInDummySpan(Element&); void deleteInsignificantText(PassRefPtr, unsigned start, unsigned end); void deleteInsignificantText(const Position& start, const Position& end); void deleteInsignificantTextDownstream(const Position&); - PassRefPtr appendBlockPlaceholder(PassRefPtr); - PassRefPtr insertBlockPlaceholder(const Position&); - PassRefPtr addBlockPlaceholderIfNeeded(Element*); + RefPtr appendBlockPlaceholder(PassRefPtr); + RefPtr insertBlockPlaceholder(const Position&); + RefPtr addBlockPlaceholderIfNeeded(Element*); void removePlaceholderAt(const Position&); - PassRefPtr insertNewDefaultParagraphElementAt(const Position&); + Ref insertNewDefaultParagraphElementAt(const Position&); - PassRefPtr moveParagraphContentsToNewBlockIfNecessary(const Position&); + RefPtr 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 shouldBreakOutOfEmptyListItem() const; bool breakOutOfEmptyListItem(); bool breakOutOfEmptyMailBlockquotedParagraph(); Position positionAvoidingSpecialElementBoundary(const Position&); - PassRefPtr splitTreeToNode(Node*, Node*, bool splitAncestor = false); + RefPtr splitTreeToNode(Node*, Node*, bool splitAncestor = false); Vector> m_commands; private: - virtual bool isCompositeEditCommand() const override { return true; } + bool isCompositeEditCommand() const override { return true; } RefPtr 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 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 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 create(Document& document, const String& linkURL) + static Ref 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::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/DeleteButton.h b/Source/WebCore/editing/DeleteButton.h deleted file mode 100644 index b98ba06a7..000000000 --- a/Source/WebCore/editing/DeleteButton.h +++ /dev/null @@ -1,49 +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. - */ - -#ifndef DeleteButton_h -#define DeleteButton_h - -#include "HTMLImageElement.h" - -namespace WebCore { - -class DeleteButton : public HTMLImageElement { -public: - static PassRefPtr create(Document&); - -#if !PLATFORM(IOS) - virtual bool willRespondToMouseClickEvents() override { return true; } -#endif // !PLATFORM(IOS) - -private: - explicit DeleteButton(Document&); - - virtual void defaultEventHandler(Event*); -}; - -} // namespace - -#endif 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. - 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 = 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 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 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 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 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 create(Document& document, PassRefPtr target) - { - return adoptRef(new RemoveTargetCommand(document, target)); - } - -private: - RemoveTargetCommand(Document& document, PassRefPtr target) - : CompositeEditCommand(document) - , m_target(target) - { } - - void doApply() - { - removeNode(m_target); - } - -private: - RefPtr 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 m_target; - RefPtr m_containerElement; - RefPtr m_outlineElement; - RefPtr 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 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 node, unsigned offset, unsigned count) - : SimpleEditCommand(node->document()) +DeleteFromTextNodeCommand::DeleteFromTextNodeCommand(RefPtr&& 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 create(PassRefPtr node, unsigned offset, unsigned count) + static Ref create(RefPtr&& 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, unsigned offset, unsigned count); +protected: + DeleteFromTextNodeCommand(RefPtr&&, 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&) override; + void getNodesInCommand(HashSet&) override; #endif RefPtr 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(renderer)) + return; + if (downcast(*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, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) { if (!node) @@ -372,38 +403,34 @@ void DeleteSelectionCommand::removeNode(PassRefPtr 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(*node)); + auto& element = downcast(*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 = m_selectionToDelete.toNormalizedRange(); RefPtr node = range->firstNode(); while (node && node != range->pastLastNode()) { - RefPtr nextNode = NodeTraversal::next(node.get()); - if ((node->hasTagName(styleTag) && !(toElement(node.get())->hasAttribute(scopedAttr))) || node->hasTagName(linkTag)) { - nextNode = NodeTraversal::nextSkippingChildren(node.get()); + RefPtr nextNode = NodeTraversal::next(*node); + if ((is(*node) && !downcast(*node).hasAttributeWithoutSynchronization(scopedAttr)) || is(*node)) { + nextNode = NodeTraversal::nextSkippingChildren(*node); RefPtr 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(*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(*startNode)) { + Text& text = downcast(*startNode); + if (text.length() > static_cast(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(*startNode)) { // in a text node that needs to be trimmed - Text* text = toText(startNode); - deleteTextFromNode(text, startOffset, m_downstreamEnd.deprecatedEditingOffset() - startOffset); + Text& text = downcast(*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(startNode); if (startOffset > 0) { - if (startNode->isTextNode()) { + if (is(*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(*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(*startNode)) { + Text& text = downcast(*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 nextNode = NodeTraversal::nextSkippingChildren(node.get()); + node = nullptr; + } else if (!m_downstreamEnd.deprecatedNode()->isDescendantOf(*node)) { + RefPtr 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(*m_downstreamEnd.deprecatedNode())) { // in a text node that needs to be trimmed - Text* text = toText(m_downstreamEnd.deprecatedNode()); + Text& text = downcast(*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(*m_leadingWhitespace.deprecatedNode())) { + Text& textNode = downcast(*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(*m_trailingWhitespace.deprecatedNode())) { + Text& textNode = downcast(*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 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 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 rangeOfFirstCharacter = Range::create(document(), startOfSelection.deepEquivalent(), nextPosition.deepEquivalent()); - Vector markers = document().markers().markersInRange(rangeOfFirstCharacter.get(), DocumentMarker::Autocorrected); - for (size_t i = 0; i < markers.size(); ++i) { - const DocumentMarker* marker = markers[i]; + Vector 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 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(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(node)) { + Text& textNode = downcast(*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 create(Document& document, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = false, bool sanitizeMarkup = true) + static Ref 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 create(const VisibleSelection& selection, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = false, bool sanitizeMarkup = true) + static Ref 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, ShouldAssumeContentIsAlwaysEditable = DoNotAssumeContentIsAlwaysEditable); - virtual void deleteTextFromNode(PassRefPtr, unsigned, unsigned); + void removeNode(PassRefPtr, ShouldAssumeContentIsAlwaysEditable = DoNotAssumeContentIsAlwaysEditable) override; + void deleteTextFromNode(PassRefPtr, 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 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 create(const Vector& alternatives) + static Ref create(const Vector& 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& alternatives, const VisibleSelection& selectionForInsertion) +void DictationCommand::insertText(Document& document, const String& text, const Vector& alternatives, const VisibleSelection& selectionForInsertion) { - RefPtr frame = document->frame(); + RefPtr frame = document.frame(); ASSERT(frame); VisibleSelection currentSelection = frame->selection().selection(); @@ -97,25 +97,26 @@ void DictationCommand::insertText(Document* document, const String& text, const RefPtr 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()); - applyTextInsertionCommand(frame.get(), cmd, selectionForInsertion, currentSelection); + cmd = DictationCommand::create(document, newText, Vector()); + 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 alternativesInLine; collectDictationAlternativesInRange(lineStart, lineLength, alternativesInLine); - RefPtr command = InsertTextCommand::createWithMarkerSupplier(document(), m_textToInsert.substring(lineStart, lineLength), DictationMarkerSupplier::create(alternativesInLine)); + RefPtr 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& 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& alternatives, const VisibleSelection&); - virtual bool isDictationCommand() const { return true; } + static void insertText(Document&, const String&, const Vector& alternatives, const VisibleSelection&); + bool isDictationCommand() const override { return true; } private: - static PassRefPtr create(Document& document, const String& text, const Vector& alternatives) + static Ref create(Document& document, const String& text, const Vector& alternatives) { - return adoptRef(new DictationCommand(document, text, alternatives)); + return adoptRef(*new DictationCommand(document, text, alternatives)); } DictationCommand(Document&, const String& text, const Vector& 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 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 + +OBJC_CLASS NSAttributedString; +OBJC_CLASS NSDictionary; + +namespace WebCore { + +struct DictionaryPopupInfo { + FloatPoint origin; + TextIndicatorData textIndicator; +#if PLATFORM(COCOA) + RetainPtr options; + RetainPtr 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(*ancestor) || (is(*ancestor) && downcast(*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& 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 { 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 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&); #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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if USE(CF) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include 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 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 copyPropertiesFromComputedStyle(Node* return copyPropertiesFromComputedStyle(computedStyle, propertiesToInclude); } -static PassRefPtr extractPropertyValue(const StyleProperties* style, CSSPropertyID propertyID) +static PassRefPtr extractPropertyValue(const StyleProperties& style, CSSPropertyID propertyID) { - return style ? style->getPropertyCSSValue(propertyID) : PassRefPtr(); + return style.getPropertyCSSValue(propertyID); } -static PassRefPtr extractPropertyValue(ComputedStyleExtractor* computedStyle, CSSPropertyID propertyID) +static PassRefPtr extractPropertyValue(ComputedStyleExtractor& computedStyle, CSSPropertyID propertyID) { - return computedStyle->propertyValue(propertyID); + return computedStyle.propertyValue(propertyID); } template -int identifierForStyleProperty(T* style, CSSPropertyID propertyID) +int identifierForStyleProperty(T& style, CSSPropertyID propertyID) { RefPtr value = extractPropertyValue(style, propertyID); - if (!value || !value->isPrimitiveValue()) + if (!is(value.get())) return 0; - return toCSSPrimitiveValue(value.get())->getValueID(); + return downcast(*value).valueID(); } -template PassRefPtr getPropertiesNotIn(StyleProperties* styleWithRedundantProperties, T* baseStyle); +template PassRefPtr 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 backgroundColorInEffect(Node*); +static RefPtr backgroundColorInEffect(Node*); class HTMLElementEquivalent { WTF_MAKE_FAST_ALLOCATED; public: - static PassOwnPtr 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 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 value = style->getPropertyCSSValue(m_propertyID); - return matches(element) && value && value->isPrimitiveValue() && toCSSPrimitiveValue(value.get())->getValueID() == m_primitiveValue->getValueID(); + RefPtr value = style.m_mutableStyle->getPropertyCSSValue(m_propertyID); + return matches(element) && is(value.get()) && downcast(*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 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 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 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 styleValue = style.m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); + if (!styleValue) + styleValue = style.m_mutableStyle->getPropertyCSSValue(CSSPropertyTextDecoration); + return is(styleValue.get()) && downcast(*styleValue).hasValue(m_primitiveValue.get()); } - static PassOwnPtr 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 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 value = attributeValueAsCSSValue(element); - RefPtr styleValue = style->getPropertyCSSValue(m_propertyID); + RefPtr value = attributeValueAsCSSValue(&element); + RefPtr styleValue = style.m_mutableStyle->getPropertyCSSValue(m_propertyID); return compareCSSValuePtr(value, styleValue); } @@ -295,25 +291,21 @@ void HTMLAttributeEquivalent::addToStyle(Element* element, EditingStyle* style) PassRefPtr 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 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 create() - { - return adoptPtr(new HTMLFontSizeEquivalent()); - } - virtual PassRefPtr attributeValueAsCSSValue(Element*) const; - -private: HTMLFontSizeEquivalent(); + + PassRefPtr attributeValueAsCSSValue(Element*) const override; }; HTMLFontSizeEquivalent::HTMLFontSizeEquivalent() @@ -324,11 +316,12 @@ HTMLFontSizeEquivalent::HTMLFontSizeEquivalent() PassRefPtr 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(TextDecorationChange::None)) + , m_strikeThroughChange(static_cast(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(colorValue)) return Color::transparent; - CSSPrimitiveValue* primitiveColor = toCSSPrimitiveValue(colorValue); - if (primitiveColor->isRGBColor()) - return primitiveColor->getRGBA32Value(); + CSSPrimitiveValue& primitiveColor = downcast(*colorValue); + if (primitiveColor.isRGBColor()) + return primitiveColor.color(); - RGBA32 rgba = 0; - CSSParser::parseColor(rgba, colorValue->cssText()); - return rgba; + return CSSParser::parseColor(colorValue->cssText()); } template -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 -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 -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 value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta); - if (!value || !value->isPrimitiveValue()) + if (!is(value.get())) return; - CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(value.get()); + CSSPrimitiveValue& primitiveValue = downcast(*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 EditingStyle::styleWithResolvedTextDecorations() const +{ + bool hasTextDecorationChanges = underlineChange() != TextDecorationChange::None || strikeThroughChange() != TextDecorationChange::None; + if (m_mutableStyle && !hasTextDecorationChanges) + return *m_mutableStyle; + + Ref style = m_mutableStyle ? m_mutableStyle->mutableCopy() : MutableStyleProperties::create(); + + Ref 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 unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); - if (!unicodeBidi || !unicodeBidi->isPrimitiveValue()) + if (!is(unicodeBidi.get())) return false; - CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID(); + CSSValueID unicodeBidiValue = downcast(*unicodeBidi).valueID(); if (unicodeBidiValue == CSSValueEmbed) { RefPtr direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection); - if (!direction || !direction->isPrimitiveValue()) + if (!is(direction.get())) return false; - writingDirection = toCSSPrimitiveValue(direction.get())->getValueID() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection; + writingDirection = downcast(*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&& 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 underline = cssValuePool.createIdentifierValue(CSSValueUnderline); + Ref lineThrough = cssValuePool.createIdentifierValue(CSSValueLineThrough); + RefPtr value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); + RefPtr valueList; + if (value && value->isValueList()) { + valueList = downcast(*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::copy() const @@ -571,6 +649,8 @@ PassRefPtr 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 parentStyle = copyPropertiesFromComputedStyle(node->parentNode(), EditingPropertiesInEffect); RefPtr 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 parentStyle = copyPropertiesFromComputedStyle(node->parentNode(), EditingPropertiesInEffect); - RefPtr nodeStyle = copyPropertiesFromComputedStyle(node, EditingPropertiesInEffect); - nodeStyle->removeEquivalentProperties(parentStyle.get()); + RefPtr 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 -TriState EditingStyle::triStateOfStyle(T* styleToCompare, ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const +TriState EditingStyle::triStateOfStyle(T& styleToCompare, ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const { - RefPtr difference = getPropertiesNotIn(m_mutableStyle.get(), styleToCompare); + if (!m_mutableStyle) + return TrueTriState; + + RefPtr 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* conflictingProperties) const +static RefPtr textDecorationValueList(const StyleProperties& properties) +{ + RefPtr value = properties.getPropertyCSSValue(CSSPropertyTextDecoration); + if (!is(value.get())) + return nullptr; + return downcast(value.get()); +} + +bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, RefPtr* 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 newInlineStyle; + if (newInlineStylePtr) { + newInlineStyle = inlineStyle->mutableCopy(); + *newInlineStylePtr = newInlineStyle; + } + + bool shouldRemoveUnderline = underlineChange() == TextDecorationChange::Remove; + bool shouldRemoveStrikeThrough = strikeThroughChange() == TextDecorationChange::Remove; + if (shouldRemoveUnderline || shouldRemoveStrikeThrough) { + if (RefPtr valueList = textDecorationValueList(*inlineStyle)) { + RefPtr newValueList = valueList->copy(); + RefPtr extractedValueList = CSSValueList::createSpaceSeparated(); + + Ref 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 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>& htmlElementEquivalents() +static const Vector>& htmlElementEquivalents() { - DEFINE_STATIC_LOCAL(Vector>, HTMLElementEquivalents, ()); + static NeverDestroyed>> 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(CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag)); + HTMLElementEquivalents.get().append(std::make_unique(CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag)); + HTMLElementEquivalents.get().append(std::make_unique(CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag)); + HTMLElementEquivalents.get().append(std::make_unique(CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag)); + HTMLElementEquivalents.get().append(std::make_unique(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag)); + HTMLElementEquivalents.get().append(std::make_unique(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(CSSValueUnderline, HTMLNames::uTag)); + HTMLElementEquivalents.get().append(std::make_unique(CSSValueLineThrough, HTMLNames::sTag)); + HTMLElementEquivalents.get().append(std::make_unique(CSSValueLineThrough, HTMLNames::strikeTag)); } return HTMLElementEquivalents; @@ -783,14 +919,13 @@ static const Vector>& htmlElementEquivalents() bool EditingStyle::conflictsWithImplicitStyleOfElement(HTMLElement* element, EditingStyle* extractedStyle, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const { - if (!m_mutableStyle) + if (isEmpty()) return false; - const Vector>& 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>& 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>& htmlAttributeEquivalents() +static const Vector>& htmlAttributeEquivalents() { - DEFINE_STATIC_LOCAL(Vector>, HTMLAttributeEquivalents, ()); + static NeverDestroyed>> 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(CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr)); + HTMLAttributeEquivalents.get().append(std::make_unique(CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr)); + HTMLAttributeEquivalents.get().append(std::make_unique()); - HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyDirection, HTMLNames::dirAttr)); - HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyUnicodeBidi, HTMLNames::dirAttr)); + HTMLAttributeEquivalents.get().append(std::make_unique(CSSPropertyDirection, HTMLNames::dirAttr)); + HTMLAttributeEquivalents.get().append(std::make_unique(CSSPropertyUnicodeBidi, HTMLNames::dirAttr)); } return HTMLAttributeEquivalents; @@ -820,13 +955,12 @@ static const Vector>& htmlAttributeEquivalents() bool EditingStyle::conflictsWithImplicitStyleOfAttributes(HTMLElement* element) const { ASSERT(element); - if (!m_mutableStyle) + if (isEmpty()) return false; - const Vector>& 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>& 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>& 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 value = computedStyle.propertyValue(CSSPropertyTextDecoration)) { + if (value->isValueList()) { + auto& cssValuePool = CSSValuePool::singleton(); + const CSSValueList& valueList = downcast(*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>& 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>& 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(toCSSPrimitiveValue(unicodeBidi.get())->getValueID())); - if (direction && direction->isPrimitiveValue()) - m_mutableStyle->setProperty(CSSPropertyDirection, static_cast(toCSSPrimitiveValue(direction.get())->getValueID())); + if (is(unicodeBidi.get())) { + m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast(downcast(*unicodeBidi).valueID())); + if (is(direction.get())) + m_mutableStyle->setProperty(CSSPropertyDirection, static_cast(downcast(*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 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>& 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>& 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::wrappingStyleForSerialization(Node* context, bool shouldAnnotate) +Ref EditingStyle::wrappingStyleForSerialization(Node* context, bool shouldAnnotate) { - RefPtr 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::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(*node) && !isMailBlockquote(node)) { + wrappingStyle->mergeInlineAndImplicitStyleOfElement(downcast(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 underline = cssValuePool().createIdentifierValue(CSSValueUnderline); - RefPtr lineThrough = cssValuePool().createIdentifierValue(CSSValueLineThrough); + auto& cssValuePool = CSSValuePool::singleton(); + Ref underline = cssValuePool.createIdentifierValue(CSSValueUnderline); + Ref 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 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(*property.value()) && value) { + if (is(*value)) { + auto newValue = downcast(*value).copy(); + mergeTextDecorationValues(newValue, downcast(*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 styleFromMatchedRulesForElement(Element* element, unsigned rulesToInclude) +static Ref styleFromMatchedRulesForElement(Element* element, unsigned rulesToInclude) { - RefPtr style = MutableStyleProperties::create(); - Vector> matchedRules = element->document().ensureStyleResolver().styleRulesForElement(element, rulesToInclude); - for (unsigned i = 0; i < matchedRules.size(); ++i) { - if (matchedRules[i]->isStyleRule()) - style->mergeAndOverrideOnConflict(static_pointer_cast(matchedRules[i])->properties()); + auto style = MutableStyleProperties::create(); + for (auto& matchedRule : element->styleResolver().styleRulesForElement(element, rulesToInclude)) { + if (matchedRule->isStyleRule()) + style->mergeAndOverrideOnConflict(static_pointer_cast(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(*value)) continue; - if (toCSSPrimitiveValue(value)->isPercentage()) { - if (RefPtr computedPropertyValue = computedStyle.propertyValue(property.id())) - fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue.release())); + if (downcast(*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 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 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 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 defaultStyle = styleFromMatchedRulesForElement(element, StyleResolver::UAAndUserCSSRules); + RefPtr defaultStyle = styleFromMatchedRulesForElement(element, StyleResolver::UAAndUserCSSRules); removePropertiesInStyle(m_mutableStyle.get(), defaultStyle.get()); } +template +void EditingStyle::removeEquivalentProperties(T& style) +{ + Vector 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 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 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 absolute = cssValuePool.createIdentifierValue(CSSValueAbsolute); + if (m_mutableStyle->propertyMatches(CSSPropertyPosition, absolute.get())) + return true; + return false; +} + +bool EditingStyle::isFloating() +{ + RefPtr v = m_mutableStyle->getPropertyCSSValue(CSSPropertyFloat); + RefPtr noneValue = CSSValuePool::singleton().createIdentifierValue(CSSValueNone); + return v && !v->equals(*noneValue); +} + int EditingStyle::legacyFontSize(Document* document) const { RefPtr cssValue = m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize); - if (!cssValue || !cssValue->isPrimitiveValue()) + if (!is(cssValue.get())) return 0; - return legacyFontSizeFromCSSValue(document, toCSSPrimitiveValue(cssValue.get()), + return legacyFontSizeFromCSSValue(document, downcast(cssValue.get()), m_shouldUseFixedDefaultFontSize, AlwaysUseLegacyFontSize); } @@ -1257,7 +1449,7 @@ PassRefPtr EditingStyle::styleAtSelectionStart(const VisibleSelect // and find the background color of the common ancestor. if (shouldUseBackgroundColorInEffect && (selection.isRange() || hasTransparentBackgroundColor(style->m_mutableStyle.get()))) { RefPtr range(selection.toNormalizedRange()); - if (PassRefPtr 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 unicodeBidi = ComputedStyleExtractor(n).propertyValue(CSSPropertyUnicodeBidi); - if (!unicodeBidi || !unicodeBidi->isPrimitiveValue()) + if (!is(unicodeBidi.get())) continue; - CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID(); + CSSValueID unicodeBidiValue = downcast(*unicodeBidi).valueID(); if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride) return NaturalWritingDirection; } @@ -1316,10 +1508,10 @@ WritingDirection EditingStyle::textDirectionForSelection(const VisibleSelection& ComputedStyleExtractor computedStyle(node); RefPtr unicodeBidi = computedStyle.propertyValue(CSSPropertyUnicodeBidi); - if (!unicodeBidi || !unicodeBidi->isPrimitiveValue()) + if (!is(unicodeBidi.get())) continue; - CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID(); + CSSValueID unicodeBidiValue = downcast(*unicodeBidi).valueID(); if (unicodeBidiValue == CSSValueNormal) continue; @@ -1328,10 +1520,10 @@ WritingDirection EditingStyle::textDirectionForSelection(const VisibleSelection& ASSERT(unicodeBidiValue == CSSValueEmbed); RefPtr direction = computedStyle.propertyValue(CSSPropertyDirection); - if (!direction || !direction->isPrimitiveValue()) + if (!is(direction.get())) continue; - CSSValueID directionValue = toCSSPrimitiveValue(direction.get())->getValueID(); + CSSValueID directionValue = downcast(*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 mutableStyle = getPropertiesNotIn(style->style(), &computedStyle); + RefPtr 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 value = computedStyle.propertyValue(CSSPropertyWebkitTextDecorationsInEffect); + if (!is(value.get())) + value = computedStyle.propertyValue(CSSPropertyTextDecoration); + + RefPtr valueList; + if (is(value.get())) + valueList = downcast(value.get()); + + auto& cssValuePool = CSSValuePool::singleton(); + Ref underline = cssValuePool.createIdentifierValue(CSSValueUnderline); + bool hasUnderline = valueList && valueList->hasValue(underline.ptr()); + + Ref 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 textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration); - if (textDecoration && textDecoration->isValueList()) { - RefPtr underline = cssValuePool().createIdentifierValue(CSSValueUnderline); - RefPtr lineThrough = cssValuePool().createIdentifierValue(CSSValueLineThrough); + RefPtr textDecoration = style.getPropertyCSSValue(CSSPropertyTextDecoration); + if (is(textDecoration.get())) { + auto& cssValuePool = CSSValuePool::singleton(); + RefPtr underline = cssValuePool.createIdentifierValue(CSSValueUnderline); + RefPtr lineThrough = cssValuePool.createIdentifierValue(CSSValueLineThrough); - RefPtr newTextDecoration = toCSSValueList(textDecoration.get())->copy(); + RefPtr newTextDecoration = downcast(*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 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 fontSize = style.getPropertyCSSValue(CSSPropertyFontSize)) { + if (!is(*fontSize)) + style.removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size. + else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, downcast(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 textDecoration = style->getPropertyCSSValue(propertID); - if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList()) + RefPtr textDecoration = style.getPropertyCSSValue(propertID); + if (!is(textDecoration.get()) || !is(refTextDecoration)) return; - RefPtr newTextDecoration = toCSSValueList(textDecoration.get())->copy(); - CSSValueList* valuesInRefTextDecoration = toCSSValueList(refTextDecoration); + RefPtr newTextDecoration = downcast(*textDecoration).copy(); - for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++) - newTextDecoration->removeAll(valuesInRefTextDecoration->item(i)); + for (auto& value : downcast(*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(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(fontWeight).valueID()) { case CSSValue100: case CSSValue200: case CSSValue300: @@ -1525,41 +1763,42 @@ static bool fontWeightIsBold(CSSValue* fontWeight) } template -static bool fontWeightIsBold(T* style) +static bool fontWeightIsBold(T& style) { - return fontWeightIsBold(extractPropertyValue(style, CSSPropertyFontWeight).get()); + RefPtr fontWeight = extractPropertyValue(style, CSSPropertyFontWeight); + return fontWeight && fontWeightIsBold(*fontWeight); } template -static PassRefPtr extractPropertiesNotIn(StyleProperties* styleWithRedundantProperties, T* baseStyle) +static Ref extractPropertiesNotIn(StyleProperties& styleWithRedundantProperties, T& baseStyle) { - ASSERT(styleWithRedundantProperties); - RefPtr result = styleWithRedundantProperties->mutableCopy(); - + RefPtr result = EditingStyle::create(&styleWithRedundantProperties); result->removeEquivalentProperties(baseStyle); + ASSERT(result->style()); + Ref mutableStyle = *result->style(); RefPtr 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 -PassRefPtr getPropertiesNotIn(StyleProperties* styleWithRedundantProperties, T* baseStyle) +PassRefPtr 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(*value)) return false; - CSSPrimitiveValue* value = toCSSPrimitiveValue(cssValue); - if (value->isRGBColor()) - return !alphaChannel(value->getRGBA32Value()); - return value->getValueID() == CSSValueTransparent; + auto& primitiveValue = downcast(*value); + if (primitiveValue.isRGBColor()) + return !primitiveValue.color().isVisible(); + return primitiveValue.valueID() == CSSValueTransparent; } bool hasTransparentBackgroundColor(StyleProperties* style) { - RefPtr cssValue = style->getPropertyCSSValue(CSSPropertyBackgroundColor); - return isTransparentColorValue(cssValue.get()); + return isTransparentColorValue(style->getPropertyCSSValue(CSSPropertyBackgroundColor).get()); } -PassRefPtr backgroundColorInEffect(Node* node) +RefPtr backgroundColorInEffect(Node* node) { for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) { - if (RefPtr 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 #include @@ -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 { public: @@ -69,38 +72,50 @@ public: enum ShouldExtractMatchingStyle { ExtractMatchingStyle, DoNotExtractMatchingStyle }; static float NoFontDelta; - static PassRefPtr create() + static Ref create() + { + return adoptRef(*new EditingStyle); + } + + static Ref create(Node* node, PropertiesToInclude propertiesToInclude = OnlyEditingInheritableProperties) + { + return adoptRef(*new EditingStyle(node, propertiesToInclude)); + } + + static Ref create(const Position& position, PropertiesToInclude propertiesToInclude = OnlyEditingInheritableProperties) { - return adoptRef(new EditingStyle()); + return adoptRef(*new EditingStyle(position, propertiesToInclude)); } - static PassRefPtr create(Node* node, PropertiesToInclude propertiesToInclude = OnlyEditingInheritableProperties) + static Ref create(const StyleProperties* style) { - return adoptRef(new EditingStyle(node, propertiesToInclude)); + return adoptRef(*new EditingStyle(style)); } - static PassRefPtr create(const Position& position, PropertiesToInclude propertiesToInclude = OnlyEditingInheritableProperties) + static Ref create(const CSSStyleDeclaration* style) { - return adoptRef(new EditingStyle(position, propertiesToInclude)); + return adoptRef(*new EditingStyle(style)); } - static PassRefPtr create(const StyleProperties* style) + static Ref create(CSSPropertyID propertyID, const String& value) { - return adoptRef(new EditingStyle(style)); + return adoptRef(*new EditingStyle(propertyID, value)); } - static PassRefPtr create(CSSPropertyID propertyID, const String& value) + static Ref 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 styleWithResolvedTextDecorations() const; bool textDirection(WritingDirection&) const; bool isEmpty() const; void setStyle(PassRefPtr); void overrideWithStyle(const StyleProperties*); + void overrideTypingStyleAt(const EditingStyle&, const Position&); void clear(); PassRefPtr copy() const; PassRefPtr extractAndRemoveBlockProperties(); @@ -108,16 +123,18 @@ public: void removeBlockProperties(); void removeStyleAddedByNode(Node*); void removeStyleConflictingWithStyleOfNode(Node*); + template 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& conflictingProperties) const + bool conflictsWithInlineStyleOfElement(StyledElement* element, RefPtr& 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& conflictingAttributes, ShouldExtractMatchingStyle) const; @@ -129,58 +146,62 @@ public: void mergeTypingStyle(Document&); enum CSSPropertyOverrideMode { OverrideValues, DoNotOverrideValues }; void mergeInlineStyleOfElement(StyledElement*, CSSPropertyOverrideMode, PropertiesToInclude = AllProperties); - static PassRefPtr wrappingStyleForSerialization(Node* context, bool shouldAnnotate); + static Ref 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(change); } + TextDecorationChange underlineChange() const { return static_cast(m_underlineChange); } + void setStrikeThroughChange(TextDecorationChange change) { m_strikeThroughChange = static_cast(change); } + TextDecorationChange strikeThroughChange() const { return static_cast(m_strikeThroughChange); } - static PassRefPtr styleAtSelectionStart(const VisibleSelection&, bool shouldUseBackgroundColorInEffect = false); + WEBCORE_EXPORT static PassRefPtr 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 - TriState triStateOfStyle(T* styleToCompare, ShouldIgnoreTextOnlyProperties) const; - bool conflictsWithInlineStyleOfElement(StyledElement*, EditingStyle* extractedStyle, Vector* conflictingProperties) const; + template TriState triStateOfStyle(T& styleToCompare, ShouldIgnoreTextOnlyProperties) const; + bool conflictsWithInlineStyleOfElement(StyledElement*, RefPtr* newInlineStyle, EditingStyle* extractedStyle) const; void mergeInlineAndImplicitStyleOfElement(StyledElement*, CSSPropertyOverrideMode, PropertiesToInclude); void mergeStyle(const StyleProperties*, CSSPropertyOverrideMode); RefPtr 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 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 -#include -#if ENABLE(DELETION_UI) -#include "DeleteButtonController.h" -#endif - -#if PLATFORM(IOS) -#include "DictationCommandIOS.h" -#include -#include +#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 = nullptr, const Vector>& 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 = nullptr, const Vector>& 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); private: - virtual EditAction editingAction() const; + EditAction editingAction() const override; }; ClearTextCommand::ClearTextCommand(Document& document) @@ -133,73 +174,15 @@ void ClearTextCommand::CreateAndApply(const RefPtr frame) const VisibleSelection oldSelection = frame->selection().selection(); frame->selection().selectAll(); - RefPtr 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 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(*event->target()->toNode()) ? downcast(event->target()->toNode()) : nullptr; if (textFromControlOfTarget && (selection.start().isNull() || textFromControlOfTarget != textFormControlOfSelectionStart)) { if (RefPtr 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(node)) + return nullptr; + return downcast(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 > > dictationPhrases, RetainPtr metadata) -{ - if (m_frame.selection().isNone()) - return; - - if (dictationPhrases->isEmpty()) - return; - - applyCommand(DictationCommandIOS::create(document(), dictationPhrases, metadata)); -} - -void Editor::setDictationPhrasesAsChildOfElement(PassOwnPtr > > dictationPhrases, RetainPtr 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 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(element->firstChild()); - int previousDictationPhraseStart = 0; - for (size_t i = 0; i < dictationPhraseCount; i++) { - const Vector& interpretations = dictationPhrases->at(i); - int dictationPhraseLength = interpretations[0].length(); - int dictationPhraseEnd = previousDictationPhraseStart + dictationPhraseLength; - if (interpretations.size() > 1) { - RefPtr dictationPhraseRange = Range::create(document(), textNode, previousDictationPhraseStart, textNode, dictationPhraseEnd); - document().markers().addDictationPhraseWithAlternativesMarker(dictationPhraseRange.get(), interpretations); - } - previousDictationPhraseStart = dictationPhraseEnd; - } - - RefPtr 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 pastingFragment, bool smartReplace, bool matchStyle) +void Editor::pasteAsFragment(Ref&& 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 = selectedRange(); - if (!range) - return; - - bool chosePlainText; - RefPtr 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 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(child) && fragment->lastChild() == child) + return client()->shouldInsertText(downcast(*child).data(), replacingDOMRange.get(), givenAction); } return client()->shouldInsertNode(fragment.get(), replacingDOMRange.get(), givenAction); } -void Editor::replaceSelectionWithFragment(PassRefPtr fragment, bool selectReplacement, bool smartReplace, bool matchStyle) +void Editor::replaceSelectionWithFragment(PassRefPtr 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 fragment, options |= ReplaceSelectionCommand::SmartReplace; if (matchStyle) options |= ReplaceSelectionCommand::MatchStyle; - applyCommand(ReplaceSelectionCommand::create(document(), fragment, options, EditActionPaste)); + if (mailBlockquoteHandling == MailBlockquoteHandling::IgnoreBlockquote) + options |= ReplaceSelectionCommand::IgnoreMailBlockquote; + + RefPtr 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 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 = selectedRange(); if (!range) return; - replaceSelectionWithFragment(createFragmentFromText(*range, text), selectReplacement, smartReplace, true); + replaceSelectionWithFragment(createFragmentFromText(*range, text), selectReplacement, smartReplace, true, editingAction); } -PassRefPtr Editor::selectedRange() +RefPtr 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 - 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 parent = elem->parentNode(); - RefPtr siblingAfter = elem->nextSibling(); - if (parent) - elem->remove(ec); - - RefPtr context = document().createRange(); - context->selectNodeContents(elem, ec); - RefPtr 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(*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(*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 Editor::increaseSelectionListLevel() +RefPtr Editor::increaseSelectionListLevel() { if (!canEditRichly() || m_frame.selection().isNone()) - return 0; + return nullptr; RefPtr newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevel(&document()); revealSelectionAfterEditingOperation(); return newList; } -PassRefPtr Editor::increaseSelectionListLevelOrdered() +RefPtr Editor::increaseSelectionListLevelOrdered() { if (!canEditRichly() || m_frame.selection().isNone()) - return 0; + return nullptr; RefPtr newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(&document()); revealSelectionAfterEditingOperation(); - return newList.release(); + return newList; } -PassRefPtr Editor::increaseSelectionListLevelUnordered() +RefPtr Editor::increaseSelectionListLevelUnordered() { if (!canEditRichly() || m_frame.selection().isNone()) - return 0; + return nullptr; RefPtr 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::createForCopyAndPaste(policy); + auto dataTransfer = DataTransfer::createForCopyAndPaste(policy); - RefPtr 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::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&& 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 = 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 = 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&& 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 prpStartRoot, PassRefPtr 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 startRoot, RefPtr endRoot, const AtomicString& inputTypeName, const String& data = { }, RefPtr&& dataTransfer = nullptr, const Vector>& targetRanges = { }, bool cancelable = true) { - RefPtr startRoot = prpStartRoot; - RefPtr 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 startRoot, RefPtr endRoot, const AtomicString& inputTypeName, const String& data = { }, RefPtr&& dataTransfer = nullptr, const Vector>& 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>&& 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 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 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 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()) + , m_spellChecker(std::make_unique(frame)) + , m_alternativeTextController(std::make_unique(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 = 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 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 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(focusedElement)) { if (direction == NaturalWritingDirection) return; - toHTMLElement(focusedElement)->setAttribute(dirAttr, direction == LeftToRightWritingDirection ? "ltr" : "rtl"); - focusedElement->dispatchInputEvent(); + + auto& focusedFormElement = downcast(*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 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& underlines, unsigned selectionStart, unsigned selectionEnd) { + Ref protection(m_frame); + UserTypingGestureIndicator typingGestureIndicator(m_frame); setIgnoreCompositionSelectionChange(true); @@ -1791,6 +1741,18 @@ void Editor::setComposition(const String& text, const VectorstartDelayingAndCoalescingContentChangeNotifications(); #endif @@ -1817,47 +1779,46 @@ void Editor::setComposition(const String& text, const VectordispatchEvent(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(baseNode) && baseNode == extentNode && baseOffset + text.length() == extentOffset) { + m_compositionNode = downcast(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 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 ) - 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 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 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 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 Editor::guessesForMisspelledWord(const String& word) const Vector guesses; if (client()) - textChecker()->getGuessesForWord(word, String(), guesses); + textChecker()->getGuessesForWord(word, String(), m_frame.selection().selection(), guesses); return guesses; } @@ -2161,13 +2129,13 @@ Vector Editor::guessesForMisspelledOrUngrammatical(bool& misspelled, boo { if (unifiedTextCheckerEnabled()) { RefPtr 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(); 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 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(*node) ? downcast(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& 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 paragraphRange = paragraphToCheck.paragraphRange(); bool asynchronous = m_frame.settings().asynchronousSpellCheckingEnabled() && !shouldShowCorrectionPanel; // In asynchronous mode, we intentionally check paragraph-wide sentence. - RefPtr 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 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 rangeToReplace, const String& replacement, int resultLocation, int resultLength) { - ContainerNode* scope = toContainerNode(highestAncestor(paragraph.paragraphRange()->startContainer())); + auto& scope = downcast(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 , http://webkit.org/b/89526. - RefPtr newParagraphRange = TextIterator::rangeFromLocationAndLength(scope, paragraphLocation, paragraphLength + replacement.length() - resultLength); + RefPtr newParagraphRange = TextIterator::rangeFromLocationAndLength(&scope, paragraphLocation, paragraphLength + replacement.length() - resultLength); paragraph = TextCheckingParagraph(TextIterator::subrange(newParagraphRange.get(), resultLocation, replacement.length()), newParagraphRange); } void Editor::markAndReplaceFor(PassRefPtr request, const Vector& results) { + Ref protection(m_frame); ASSERT(request); TextCheckingTypeMask textCheckingOptions = request->data().mask(); @@ -2479,7 +2454,7 @@ void Editor::markAndReplaceFor(PassRefPtr 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 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 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 request, const Vect RefPtr 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& 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 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 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 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 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 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 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 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 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 wordRange = Range::create(document(), startOfFirstWord.deepEquivalent(), endOfLastWord.deepEquivalent()); - Vector markers = document().markers().markersInRange(wordRange.get(), DocumentMarker::DictationAlternatives); - for (size_t i = 0; i < markers.size(); ++i) - m_alternativeTextController->removeDictationAlternativesForMarker(markers[i]); + Vector 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 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 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 Editor::compositionRange() const +RefPtr 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 = 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 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 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 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 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 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(*element)); + if (!is(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(*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 protection(m_frame); + VisibleSelection selection = m_frame.selection().selection(); RefPtr 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 Editor::findStringAndScrollToVisible(const String& target, Range* previousMatch, FindOptions options) -{ - RefPtr 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 Editor::rangeOfString(const String& target, Range* referenceRange, FindOptions options) +RefPtr 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 Editor::rangeOfString(const String& target, Range* referenceRa searchRange->setEnd(startInReferenceRange ? referenceRange->endPosition() : referenceRange->startPosition()); } - RefPtr shadowTreeRoot = referenceRange && referenceRange->startContainer() ? referenceRange->startContainer()->nonBoundaryShadowTreeRootNode() : 0; + RefPtr 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 resultRange(findPlainText(searchRange.get(), target, options)); + RefPtr 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 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>* 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 resultRange(findPlainText(searchRange.get(), target, options & ~Backwards)); - if (resultRange->collapsed(IGNORE_EXCEPTION)) { - if (!resultRange->startContainer()->isInShadowTree()) + RefPtr 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 + 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> markedRanges; + + FrameSelection& frameSelection = m_frame.selection(); + if (!frameSelection.isRange()) { + m_frame.mainFrame().servicesOverlayController().selectedTelephoneNumberRangesChanged(); + return; + } + RefPtr 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 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>& 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 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(*node)) + node = downcast(*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(from); unsigned int endOffset = static_cast(from + length); - Vector markers = document().markers().markersFor(node); - for (size_t i = 0; i < markers.size(); ++i) { - DocumentMarker* marker = markers[i]; + Vector 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(host) && downcast(*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 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 rangeForCurrentlyTypedString = candidateRangeForSelection(m_frame); + if (rangeForCurrentlyTypedString && candidateWouldReplaceText(selection)) + return plainText(rangeForCurrentlyTypedString.get()); + + return String(); +} + +RefPtr Editor::contextRangeForCandidateRequest() const +{ + const VisibleSelection& selection = m_frame.selection().selection(); + return makeRange(startOfParagraph(selection.visibleStart()), endOfParagraph(selection.visibleEnd())); +} + +RefPtr Editor::rangeForTextCheckingResult(const TextCheckingResult& result) const +{ + if (!result.length) + return nullptr; + + RefPtr 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 replaceCommand = ReplaceRangeWithTextCommand::create(range.get(), acceptedCandidate.replacement); + applyCommand(replaceCommand.ptr()); + } + } else + insertText(acceptedCandidate.replacement, nullptr); + + RefPtr 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 Editor::dictationAlternativesForMarker(const DocumentMarker* marker) +Vector Editor::dictationAlternativesForMarker(const DocumentMarker& marker) { return m_alternativeTextController->dictationAlternativesForMarker(marker); } @@ -3472,4 +3647,108 @@ Document& Editor::document() const return *m_frame.document(); } +RefPtr 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 = 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 ! +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 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 = 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 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 -#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 fragment; + Vector> resources; +}; + +#endif + class Editor { + WTF_MAKE_FAST_ALLOCATED; public: - static PassOwnPtr 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, PassRefPtr, 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 insertOrderedList(); - PassRefPtr insertUnorderedList(); - bool canIncreaseSelectionListLevel(); - bool canDecreaseSelectionListLevel(); - PassRefPtr increaseSelectionListLevel(); - PassRefPtr increaseSelectionListLevelOrdered(); - PassRefPtr increaseSelectionListLevelUnordered(); - void decreaseSelectionListLevel(); + WEBCORE_EXPORT PassRefPtr insertOrderedList(); + WEBCORE_EXPORT PassRefPtr insertUnorderedList(); + WEBCORE_EXPORT bool canIncreaseSelectionListLevel(); + WEBCORE_EXPORT bool canDecreaseSelectionListLevel(); + WEBCORE_EXPORT RefPtr increaseSelectionListLevel(); + WEBCORE_EXPORT RefPtr increaseSelectionListLevelOrdered(); + WEBCORE_EXPORT RefPtr 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&&, EditAction); void applyParagraphStyle(StyleProperties*, EditAction = EditActionUnspecified); - void applyStyleToSelection(StyleProperties*, EditAction); + WEBCORE_EXPORT void applyStyleToSelection(StyleProperties*, EditAction); + WEBCORE_EXPORT void applyStyleToSelection(Ref&&, EditAction); void applyParagraphStyleToSelection(StyleProperties*, EditAction); + // Returns whether or not we should proceed with editing. + bool willApplyEditing(CompositeEditCommand&, Vector>&&) const; + bool willUnapplyEditing(const EditCommandComposition&) const; + bool willReapplyEditing(const EditCommandComposition&) const; + void appliedEditing(PassRefPtr); - void unappliedEditing(PassRefPtr); - void reappliedEditing(PassRefPtr); + 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); - 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 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& dictationAlternatives, Event* triggeringEvent); + WEBCORE_EXPORT bool insertDictatedText(const String&, const Vector& 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 guessesForMisspelledWord(const String&) const; Vector 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& firstMisspellingRange); void markBadGrammar(const VisibleSelection&); void markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection); void markAndReplaceFor(PassRefPtr, const Vector&); 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&, 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&, 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 compositionRange() const; - bool getCompositionSelection(unsigned& selectionStart, unsigned& selectionEnd) const; + WEBCORE_EXPORT RefPtr 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& 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 rangeForPoint(const IntPoint& windowPoint); + WEBCORE_EXPORT PassRefPtr rangeForPoint(const IntPoint& windowPoint); void clear(); @@ -317,55 +345,60 @@ public: EditingBehavior behavior() const; - PassRefPtr selectedRange(); + RefPtr selectedRange(); #if PLATFORM(IOS) - void confirmMarkedText(); - void setTextAsChildOfElement(const String&, Element*); - void setTextAlignmentForChangedBaseWritingDirection(WritingDirection); - void insertDictationPhrases(PassOwnPtr > > dictationPhrases, RetainPtr metadata); - void setDictationPhrasesAsChildOfElement(PassOwnPtr > > dictationPhrases, RetainPtr metadata, Element* element); + WEBCORE_EXPORT void confirmMarkedText(); + WEBCORE_EXPORT void setTextAsChildOfElement(const String&, Element&); + WEBCORE_EXPORT void setTextAlignmentForChangedBaseWritingDirection(WritingDirection); + WEBCORE_EXPORT void insertDictationPhrases(Vector>&& dictationPhrases, RetainPtr metadata); + WEBCORE_EXPORT void setDictationPhrasesAsChildOfElement(const Vector>& dictationPhrases, RetainPtr 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, bool smartReplace, bool matchStyle); - void pasteAsPlainText(const String&, bool smartReplace); + WEBCORE_EXPORT void pasteAsFragment(Ref&&, 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 rangeOfString(const String&, Range*, FindOptions); - PassRefPtr findStringAndScrollToVisible(const String&, Range*, FindOptions); + WEBCORE_EXPORT RefPtr 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>*); + WEBCORE_EXPORT unsigned countMatchesForText(const String&, Range*, FindOptions, unsigned limit, bool markMatches, Vector>*); 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, 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, 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 dictationAlternativesForMarker(const DocumentMarker*); + Vector dictationAlternativesForMarker(const DocumentMarker&); void applyDictationAlternativelternative(const String& alternativeString); - PassRefPtr 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 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 fontAttributesForSelectionStart() const; + WEBCORE_EXPORT String stringSelectionForPasteboard(); String stringSelectionForPasteboardWithImageAltText(); - PassRefPtr webContentFromPasteboard(Pasteboard&, Range& context, bool allowPlainText, bool& chosePlainText); #if !PLATFORM(IOS) bool canCopyExcludingStandaloneImages(); void takeFindStringFromSelection(); - void readSelectionFromPasteboard(const String& pasteboardName); - PassRefPtr 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 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>& detectedTelephoneNumberRanges() const { return m_detectedTelephoneNumberRanges; } +#endif + + WEBCORE_EXPORT String stringForCandidateRequest() const; + WEBCORE_EXPORT void handleAcceptedCandidate(TextCheckingResult); + WEBCORE_EXPORT RefPtr contextRangeForCandidateRequest() const; + RefPtr rangeForTextCheckingResult(const TextCheckingResult&) const; + bool isHandlingAcceptedCandidate() const { return m_isHandlingAcceptedCandidate; } + + void setIsGettingDictionaryPopupInfo(bool b) { m_isGettingDictionaryPopupInfo = b; } + bool isGettingDictionaryPopupInfo() const { return m_isGettingDictionaryPopupInfo; } + + Ref 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& 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 selectionInWebArchiveFormat(); - PassRefPtr adjustedSelectionRange(); - PassRefPtr createFragmentForImageResourceAndAddResource(PassRefPtr); - PassRefPtr createFragmentAndAddResources(NSAttributedString *); + RefPtr adjustedSelectionRange(); + +#if PLATFORM(COCOA) + RefPtr selectionInWebArchiveFormat(); + String selectionInHTMLFormat(); + RefPtr imageInWebArchiveFormat(Element&); + RefPtr createFragmentForImageResourceAndAddResource(RefPtr&&); + RefPtr createFragmentAndAddResources(NSAttributedString *); + FragmentAndResources createFragment(NSAttributedString *); void fillInUserVisibleForm(PasteboardURL&); + + static RefPtr dataInRTFDFormat(NSAttributedString *); + static RefPtr dataInRTFFormat(NSAttributedString *); #endif + void postTextStateChangeNotificationForCut(const String&, const VisibleSelection&); + Frame& m_frame; -#if ENABLE(DELETION_UI) - OwnPtr m_deleteButtonController; -#endif RefPtr m_lastEditCommand; RefPtr m_compositionNode; unsigned m_compositionStart; unsigned m_compositionEnd; Vector m_customCompositionUnderlines; - bool m_ignoreCompositionSelectionChange; - bool m_shouldStartNewKillRingSequence; - bool m_shouldStyleWithCSS; - const OwnPtr m_killRing; - const OwnPtr m_spellChecker; - const OwnPtr m_alternativeTextController; + bool m_ignoreCompositionSelectionChange { false }; + bool m_shouldStartNewKillRingSequence { false }; + bool m_shouldStyleWithCSS { false }; + const std::unique_ptr m_killRing; + const std::unique_ptr m_spellChecker; + const std::unique_ptr 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>& markedRanges); + + Timer m_telephoneNumberDetectionUpdateTimer; + Vector> 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 Editor::avoidIntersectionWithDeleteButtonController(const Range* range) const -{ - return const_cast(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 CommandMap; +typedef HashMap 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&& 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 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 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 helloworld 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 selectionStyle = EditingStyle::styleAtSelectionStart(frame.selection().selection()); - if (!selectionStyle || !selectionStyle->style()) - return false; - - RefPtr selectedCSSValue = selectionStyle->style()->getPropertyCSSValue(propertyID); - String newStyle = ASCIILiteral("none"); - if (selectedCSSValue->isValueList()) { - RefPtr 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 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 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 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 content) +static bool executeInsertNode(Frame& frame, Ref&& content) { - RefPtr 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 newRange = selection.toNormalizedRange(); if (!newRange) return false; - if (newRange->collapsed(IGNORE_EXCEPTION)) + if (newRange->collapsed()) return false; - RefPtr oldRange = frame.selection().selection().toNormalizedRange(); - EAffinity affinity = frame.selection().affinity(); + RefPtr 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(renderer)) return 0; const RenderStyle& style = renderer->style(); if (!(style.overflowY() == OSCROLL || style.overflowY() == OAUTO || focusedElement->hasEditableStyle())) return 0; - int height = std::min(toRenderBox(renderer)->clientHeight(), frame.view()->visibleHeight()); - return static_cast(std::max(std::max(height * Scrollbar::minFractionToStepWhenPaging(), height - Scrollbar::maxOverlapBetweenPages()), 1)); + int height = std::min(downcast(*renderer).clientHeight(), frame.view()->visibleHeight()); + return static_cast(Scrollbar::pageStep(height)); } -static RefPtr unionDOMRanges(Range* a, Range* b) +static RefPtr 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 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 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 rule = HTMLHRElement::create(*frame.document()); + Ref 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 image = HTMLImageElement::create(*frame.document()); + Ref 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 lineThrough = CSSPrimitiveValue::createIdentifier(CSSValueLineThrough); - return executeToggleStyleInList(frame, source, EditActionUnderline, CSSPropertyWebkitTextDecorationsInEffect, lineThrough.get()); + Ref 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 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 underline = CSSPrimitiveValue::createIdentifier(CSSValueUnderline); - return executeToggleStyleInList(frame, source, EditActionUnderline, CSSPropertyWebkitTextDecorationsInEffect, underline.get()); + Ref 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,11 +1667,8 @@ 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 }; @@ -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 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(*node) && isElementForFormatBlock(downcast(*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(commonAncestor) : nullptr; } bool isElementForFormatBlock(const QualifiedName& tagName) { - DEFINE_STATIC_LOCAL(HashSet, 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> 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 create(Document& document, const QualifiedName& tagName) + static Ref 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&); - 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&) 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; } // : 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 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 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 . + 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 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(node)) return false; - Element* element = toElement(node); - return element->containsIncludingShadowDOM(position.anchorNode()); -} - -static void clearRenderViewSelection(const Position& position) -{ - Ref document(position.anchorNode()->document()); - document->updateStyleIfNeeded(); - if (RenderView* view = document->renderView()) - view->clearSelection(); + return downcast(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 = 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 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(*node) ? downcast(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(*renderer->node())) { + Element& element = downcast(*renderer->node()); + fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element.localName().string().utf8().data()); + } else if (is(*renderer)) { + RenderText& textRenderer = downcast(*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(focusedElement)) { + HTMLSelectElement& selectElement = downcast(*focusedElement); + if (selectElement.canSelectAll()) { + selectElement.selectAll(); return; } } - RefPtr root = 0; - Node* selectStartTarget = 0; - if (isContentEditable()) { + RefPtr 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(*focusedElement)) { + downcast(*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 and . - 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&) +void FrameSelection::caretBlinkTimerFired() { #if ENABLE(TEXT_CARET) ASSERT(caretIsVisible()); @@ -1914,12 +2145,6 @@ void FrameSelection::caretBlinkTimerFired(Timer&) #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 ,