summaryrefslogtreecommitdiff
path: root/Source/WebCore/editing/Editor.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/editing/Editor.cpp
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebCore/editing/Editor.cpp')
-rw-r--r--Source/WebCore/editing/Editor.cpp1775
1 files changed, 1027 insertions, 748 deletions
diff --git a/Source/WebCore/editing/Editor.cpp b/Source/WebCore/editing/Editor.cpp
index e599dbd8b..458c33bf0 100644
--- a/Source/WebCore/editing/Editor.cpp
+++ b/Source/WebCore/editing/Editor.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006, 2007, 2008, 2011, 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2006-2016 Apple Inc. All rights reserved.
* Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
*
* Redistribution and use in source and binary forms, with or without
@@ -11,10 +11,10 @@
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
@@ -33,10 +33,10 @@
#include "CSSComputedStyleDeclaration.h"
#include "CSSPropertyNames.h"
#include "CachedResourceLoader.h"
-#include "Clipboard.h"
#include "ClipboardEvent.h"
#include "CompositionEvent.h"
#include "CreateLinkCommand.h"
+#include "DataTransfer.h"
#include "DeleteSelectionCommand.h"
#include "DictationAlternative.h"
#include "DictationCommand.h"
@@ -45,31 +45,39 @@
#include "EditorClient.h"
#include "EventHandler.h"
#include "EventNames.h"
-#include "ExceptionCodePlaceholder.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameTree.h"
#include "FrameView.h"
#include "GraphicsContext.h"
+#include "HTMLCollection.h"
#include "HTMLFormControlElement.h"
#include "HTMLFrameOwnerElement.h"
#include "HTMLImageElement.h"
+#include "HTMLInputElement.h"
#include "HTMLNames.h"
-#include "HTMLTextAreaElement.h"
+#include "HTMLSpanElement.h"
#include "HitTestResult.h"
#include "IndentOutdentCommand.h"
+#include "InputEvent.h"
#include "InsertListCommand.h"
+#include "InsertTextCommand.h"
#include "KeyboardEvent.h"
#include "KillRing.h"
+#include "Logging.h"
+#include "MainFrame.h"
#include "ModifySelectionListLevel.h"
#include "NodeList.h"
#include "NodeTraversal.h"
#include "Page.h"
#include "Pasteboard.h"
+#include "Range.h"
#include "RemoveFormatCommand.h"
#include "RenderBlock.h"
#include "RenderTextControl.h"
+#include "RenderedDocumentMarker.h"
#include "RenderedPosition.h"
+#include "ReplaceRangeWithTextCommand.h"
#include "ReplaceSelectionCommand.h"
#include "Settings.h"
#include "ShadowRoot.h"
@@ -78,6 +86,7 @@
#include "SpellChecker.h"
#include "SpellingCorrectionCommand.h"
#include "StyleProperties.h"
+#include "TelephoneNumberDetector.h"
#include "Text.h"
#include "TextCheckerClient.h"
#include "TextCheckingHelper.h"
@@ -89,28 +98,60 @@
#include "htmlediting.h"
#include "markup.h"
#include <wtf/unicode/CharacterNames.h>
-#include <wtf/unicode/Unicode.h>
-#if ENABLE(DELETION_UI)
-#include "DeleteButtonController.h"
-#endif
-
-#if PLATFORM(IOS)
-#include "DictationCommandIOS.h"
-#include <wtf/text/StringBuilder.h>
-#include <wtf/text/WTFString.h>
+#if PLATFORM(MAC)
+#include "ServicesOverlayController.h"
#endif
namespace WebCore {
-#if PLATFORM(IOS)
+static bool dispatchBeforeInputEvent(Element& element, const AtomicString& inputType, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }, bool cancelable = true)
+{
+ if (!element.document().settings().inputEventsEnabled())
+ return true;
+
+ return element.dispatchEvent(InputEvent::create(eventNames().beforeinputEvent, inputType, true, cancelable, element.document().defaultView(), data, WTFMove(dataTransfer), targetRanges, 0));
+}
+
+static void dispatchInputEvent(Element& element, const AtomicString& inputType, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { })
+{
+ if (element.document().settings().inputEventsEnabled()) {
+ // FIXME: We should not be dispatching to the scoped queue here. Normally, input events are dispatched in CompositeEditCommand::apply after the end of the scope,
+ // but TypingCommands are special in that existing TypingCommands that are applied again fire input events *from within* the scope by calling typingAddedToOpenCommand.
+ // Instead, TypingCommands should always dispatch events synchronously after the end of the scoped queue in CompositeEditCommand::apply. To work around this for the
+ // time being, just revert back to calling dispatchScopedEvent.
+ element.dispatchScopedEvent(InputEvent::create(eventNames().inputEvent, inputType, true, false, element.document().defaultView(), data, WTFMove(dataTransfer), targetRanges, 0));
+ } else
+ element.dispatchInputEvent();
+}
+
+static String inputEventDataForEditingStyleAndAction(const StyleProperties* style, EditAction action)
+{
+ if (!style)
+ return { };
+
+ switch (action) {
+ case EditActionSetColor:
+ return style->getPropertyValue(CSSPropertyColor);
+ case EditActionSetWritingDirection:
+ return style->getPropertyValue(CSSPropertyDirection);
+ default:
+ return { };
+ }
+}
+
+static String inputEventDataForEditingStyleAndAction(EditingStyle& style, EditAction action)
+{
+ return inputEventDataForEditingStyleAndAction(style.style(), action);
+}
+
class ClearTextCommand : public DeleteSelectionCommand {
public:
ClearTextCommand(Document& document);
static void CreateAndApply(const RefPtr<Frame> frame);
private:
- virtual EditAction editingAction() const;
+ EditAction editingAction() const override;
};
ClearTextCommand::ClearTextCommand(Document& document)
@@ -133,73 +174,15 @@ void ClearTextCommand::CreateAndApply(const RefPtr<Frame> frame)
const VisibleSelection oldSelection = frame->selection().selection();
frame->selection().selectAll();
- RefPtr<ClearTextCommand> clearCommand = adoptRef(new ClearTextCommand(*frame->document()));
+ auto clearCommand = adoptRef(*new ClearTextCommand(*frame->document()));
clearCommand->setStartingSelection(oldSelection);
- applyCommand(clearCommand.release());
+ applyCommand(WTFMove(clearCommand));
}
-#endif
using namespace HTMLNames;
using namespace WTF;
using namespace Unicode;
-#if ENABLE(DELETION_UI)
-
-PassRefPtr<Range> Editor::avoidIntersectionWithDeleteButtonController(const Range* range) const
-{
- if (!range)
- return 0;
-
- Node* startContainer = range->startContainer();
- int startOffset = range->startOffset();
- Node* endContainer = range->endContainer();
- int endOffset = range->endOffset();
-
- if (!startContainer)
- return 0;
-
- ASSERT(endContainer);
-
- Element* element = m_deleteButtonController->containerElement();
- if (startContainer == element || startContainer->isDescendantOf(element)) {
- ASSERT(element->parentNode());
- startContainer = element->parentNode();
- startOffset = element->nodeIndex();
- }
- if (endContainer == element || endContainer->isDescendantOf(element)) {
- ASSERT(element->parentNode());
- endContainer = element->parentNode();
- endOffset = element->nodeIndex();
- }
-
- return Range::create(range->ownerDocument(), startContainer, startOffset, endContainer, endOffset);
-}
-
-VisibleSelection Editor::avoidIntersectionWithDeleteButtonController(const VisibleSelection& selection) const
-{
- if (selection.isNone())
- return selection;
-
- Element* element = m_deleteButtonController->containerElement();
- if (!element)
- return selection;
- VisibleSelection updatedSelection = selection;
-
- Position updatedBase = selection.base();
- updatePositionForNodeRemoval(updatedBase, element);
- if (updatedBase != selection.base())
- updatedSelection.setBase(updatedBase);
-
- Position updatedExtent = selection.extent();
- updatePositionForNodeRemoval(updatedExtent, element);
- if (updatedExtent != selection.extent())
- updatedSelection.setExtent(updatedExtent);
-
- return updatedSelection;
-}
-
-#endif
-
// When an event handler has moved the selection outside of a text control
// we should use the target control's selection for this editing operation.
VisibleSelection Editor::selectionForCommand(Event* event)
@@ -210,10 +193,10 @@ VisibleSelection Editor::selectionForCommand(Event* event)
// If the target is a text control, and the current selection is outside of its shadow tree,
// then use the saved selection for that text control.
HTMLTextFormControlElement* textFormControlOfSelectionStart = enclosingTextFormControl(selection.start());
- HTMLTextFormControlElement* textFromControlOfTarget = isHTMLTextFormControlElement(*event->target()->toNode()) ? toHTMLTextFormControlElement(event->target()->toNode()) : nullptr;
+ HTMLTextFormControlElement* textFromControlOfTarget = is<HTMLTextFormControlElement>(*event->target()->toNode()) ? downcast<HTMLTextFormControlElement>(event->target()->toNode()) : nullptr;
if (textFromControlOfTarget && (selection.start().isNull() || textFromControlOfTarget != textFormControlOfSelectionStart)) {
if (RefPtr<Range> range = textFromControlOfTarget->selection())
- return VisibleSelection(range.get(), DOWNSTREAM, selection.isDirectional());
+ return VisibleSelection(*range, DOWNSTREAM, selection.isDirectional());
}
return selection;
}
@@ -227,11 +210,10 @@ EditingBehavior Editor::behavior() const
EditorClient* Editor::client() const
{
if (Page* page = m_frame.page())
- return page->editorClient();
- return 0;
+ return &page->editorClient();
+ return nullptr;
}
-
TextCheckerClient* Editor::textChecker() const
{
if (EditorClient* owner = client())
@@ -239,59 +221,57 @@ TextCheckerClient* Editor::textChecker() const
return 0;
}
-void Editor::handleKeyboardEvent(KeyboardEvent* event)
+void Editor::handleKeyboardEvent(KeyboardEvent& event)
{
if (EditorClient* c = client())
- c->handleKeyboardEvent(event);
+ c->handleKeyboardEvent(&event);
}
-void Editor::handleInputMethodKeydown(KeyboardEvent* event)
+void Editor::handleInputMethodKeydown(KeyboardEvent& event)
{
if (EditorClient* c = client())
- c->handleInputMethodKeydown(event);
+ c->handleInputMethodKeydown(&event);
}
-bool Editor::handleTextEvent(TextEvent* event)
+bool Editor::handleTextEvent(TextEvent& event)
{
+ LOG(Editing, "Editor %p handleTextEvent (data %s)", this, event.data().utf8().data());
+
// Default event handling for Drag and Drop will be handled by DragController
// so we leave the event for it.
- if (event->isDrop())
+ if (event.isDrop())
return false;
- if (event->isPaste()) {
- if (event->pastingFragment())
+ if (event.isPaste()) {
+ if (event.pastingFragment()) {
#if PLATFORM(IOS)
- {
- if (client()->performsTwoStepPaste(event->pastingFragment()))
+ if (client()->performsTwoStepPaste(event.pastingFragment()))
return true;
#endif
- replaceSelectionWithFragment(event->pastingFragment(), false, event->shouldSmartReplace(), event->shouldMatchStyle());
-#if PLATFORM(IOS)
- }
-#endif
- else
- replaceSelectionWithText(event->data(), false, event->shouldSmartReplace());
+ replaceSelectionWithFragment(event.pastingFragment(), false, event.shouldSmartReplace(), event.shouldMatchStyle(), EditActionPaste, event.mailBlockquoteHandling());
+ } else
+ replaceSelectionWithText(event.data(), false, event.shouldSmartReplace(), EditActionPaste);
return true;
}
- String data = event->data();
+ String data = event.data();
if (data == "\n") {
- if (event->isLineBreak())
+ if (event.isLineBreak())
return insertLineBreak();
return insertParagraphSeparator();
}
- return insertTextWithoutSendingTextEvent(data, false, event);
+ return insertTextWithoutSendingTextEvent(data, false, &event);
}
bool Editor::canEdit() const
{
- return m_frame.selection().rootEditableElement();
+ return m_frame.selection().selection().rootEditableElement();
}
bool Editor::canEditRichly() const
{
- return m_frame.selection().isContentRichlyEditable();
+ return m_frame.selection().selection().isContentRichlyEditable();
}
// WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They
@@ -301,17 +281,17 @@ bool Editor::canEditRichly() const
bool Editor::canDHTMLCut()
{
- return !m_frame.selection().isInPasswordField() && !dispatchCPPEvent(eventNames().beforecutEvent, ClipboardNumb);
+ return !m_frame.selection().selection().isInPasswordField() && !dispatchCPPEvent(eventNames().beforecutEvent, DataTransferAccessPolicy::Numb);
}
bool Editor::canDHTMLCopy()
{
- return !m_frame.selection().isInPasswordField() && !dispatchCPPEvent(eventNames().beforecopyEvent, ClipboardNumb);
+ return !m_frame.selection().selection().isInPasswordField() && !dispatchCPPEvent(eventNames().beforecopyEvent, DataTransferAccessPolicy::Numb);
}
bool Editor::canDHTMLPaste()
{
- return !dispatchCPPEvent(eventNames().beforepasteEvent, ClipboardNumb);
+ return !dispatchCPPEvent(eventNames().beforepasteEvent, DataTransferAccessPolicy::Numb);
}
bool Editor::canCut() const
@@ -322,25 +302,23 @@ bool Editor::canCut() const
static HTMLImageElement* imageElementFromImageDocument(Document& document)
{
if (!document.isImageDocument())
- return 0;
+ return nullptr;
- HTMLElement* body = document.body();
+ HTMLElement* body = document.bodyOrFrameset();
if (!body)
- return 0;
+ return nullptr;
Node* node = body->firstChild();
- if (!node)
- return 0;
- if (!isHTMLImageElement(node))
- return 0;
- return toHTMLImageElement(node);
+ if (!is<HTMLImageElement>(node))
+ return nullptr;
+ return downcast<HTMLImageElement>(node);
}
bool Editor::canCopy() const
{
if (imageElementFromImageDocument(document()))
return true;
- FrameSelection& selection = m_frame.selection();
+ const VisibleSelection& selection = m_frame.selection().selection();
return selection.isRange() && !selection.isInPasswordField();
}
@@ -351,25 +329,23 @@ bool Editor::canPaste() const
bool Editor::canDelete() const
{
- FrameSelection& selection = m_frame.selection();
+ const VisibleSelection& selection = m_frame.selection().selection();
return selection.isRange() && selection.rootEditableElement();
}
bool Editor::canDeleteRange(Range* range) const
{
- Node* startContainer = range->startContainer();
- Node* endContainer = range->endContainer();
- if (!startContainer || !endContainer)
- return false;
+ Node& startContainer = range->startContainer();
+ Node& endContainer = range->endContainer();
- if (!startContainer->hasEditableStyle() || !endContainer->hasEditableStyle())
+ if (!startContainer.hasEditableStyle() || !endContainer.hasEditableStyle())
return false;
- if (range->collapsed(IGNORE_EXCEPTION)) {
+ if (range->collapsed()) {
VisiblePosition start(range->startPosition(), DOWNSTREAM);
VisiblePosition previous = start.previous();
// FIXME: We sometimes allow deletions at the start of editable roots, like when the caret is in an empty list item.
- if (previous.isNull() || previous.deepEquivalent().deprecatedNode()->rootEditableElement() != startContainer->rootEditableElement())
+ if (previous.isNull() || previous.deepEquivalent().deprecatedNode()->rootEditableElement() != startContainer.rootEditableElement())
return false;
}
return true;
@@ -390,7 +366,7 @@ bool Editor::isSelectTrailingWhitespaceEnabled()
return client() && client()->isSelectTrailingWhitespaceEnabled();
}
-bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity granularity, bool killRing, bool isTypingAction)
+bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity granularity, bool shouldAddToKillRing, bool isTypingAction)
{
if (!canEdit())
return false;
@@ -400,8 +376,8 @@ bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity g
TypingCommand::deleteKeyPressed(document(), canSmartCopyOrDelete() ? TypingCommand::SmartDelete : 0, granularity);
revealSelectionAfterEditingOperation();
} else {
- if (killRing)
- addToKillRing(selectedRange().get(), false);
+ if (shouldAddToKillRing)
+ addRangeToKillRing(*selectedRange().get(), KillRingInsertionMode::AppendText);
deleteSelectionWithSmartDelete(canSmartCopyOrDelete());
// Implicitly calls revealSelectionAfterEditingOperation().
}
@@ -409,8 +385,8 @@ bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity g
TypingCommand::Options options = 0;
if (canSmartCopyOrDelete())
options |= TypingCommand::SmartDelete;
- if (killRing)
- options |= TypingCommand::KillRing;
+ if (shouldAddToKillRing)
+ options |= TypingCommand::AddsToKillRing;
switch (direction) {
case DirectionForward:
case DirectionRight:
@@ -427,112 +403,39 @@ bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity g
// FIXME: We should to move this down into deleteKeyPressed.
// clear the "start new kill ring sequence" setting, because it was set to true
// when the selection was updated by deleting the range
- if (killRing)
+ if (shouldAddToKillRing)
setStartNewKillRingSequence(false);
return true;
}
-void Editor::deleteSelectionWithSmartDelete(bool smartDelete)
+void Editor::deleteSelectionWithSmartDelete(bool smartDelete, EditAction editingAction)
{
if (m_frame.selection().isNone())
return;
- applyCommand(DeleteSelectionCommand::create(document(), smartDelete));
+ applyCommand(DeleteSelectionCommand::create(document(), smartDelete, true, false, false, true, editingAction));
}
-#if PLATFORM(IOS)
void Editor::clearText()
{
ClearTextCommand::CreateAndApply(&m_frame);
}
-void Editor::insertDictationPhrases(PassOwnPtr<Vector<Vector<String> > > dictationPhrases, RetainPtr<id> metadata)
-{
- if (m_frame.selection().isNone())
- return;
-
- if (dictationPhrases->isEmpty())
- return;
-
- applyCommand(DictationCommandIOS::create(document(), dictationPhrases, metadata));
-}
-
-void Editor::setDictationPhrasesAsChildOfElement(PassOwnPtr<Vector<Vector<String> > > dictationPhrases, RetainPtr<id> metadata, Element* element)
-{
- // Clear the composition.
- clear();
-
- // Clear the Undo stack, since the operations that follow are not Undoable, and will corrupt the stack. Some day
- // we could make them Undoable, and let callers clear the Undo stack explicitly if they wish.
- clearUndoRedoOperations();
-
- m_frame.selection().clear();
-
- element->removeChildren();
-
- if (dictationPhrases->isEmpty()) {
- client()->respondToChangedContents();
- return;
- }
-
- ExceptionCode ec;
- RefPtr<Range> context = document().createRange();
- context->selectNodeContents(element, ec);
-
- StringBuilder dictationPhrasesBuilder;
- size_t dictationPhraseCount = dictationPhrases->size();
- for (size_t i = 0; i < dictationPhraseCount; i++) {
- const String& firstInterpretation = dictationPhrases->at(i)[0];
- dictationPhrasesBuilder.append(firstInterpretation);
- }
- String serializedDictationPhrases = dictationPhrasesBuilder.toString();
-
- element->appendChild(createFragmentFromText(*context.get(), serializedDictationPhrases), ec);
-
- // We need a layout in order to add markers below.
- document().updateLayout();
-
- if (!element->firstChild()->isTextNode()) {
- // Shouldn't happen.
- ASSERT(element->firstChild()->isTextNode());
- return;
- }
-
- Text* textNode = static_cast<Text*>(element->firstChild());
- int previousDictationPhraseStart = 0;
- for (size_t i = 0; i < dictationPhraseCount; i++) {
- const Vector<String>& interpretations = dictationPhrases->at(i);
- int dictationPhraseLength = interpretations[0].length();
- int dictationPhraseEnd = previousDictationPhraseStart + dictationPhraseLength;
- if (interpretations.size() > 1) {
- RefPtr<Range> dictationPhraseRange = Range::create(document(), textNode, previousDictationPhraseStart, textNode, dictationPhraseEnd);
- document().markers().addDictationPhraseWithAlternativesMarker(dictationPhraseRange.get(), interpretations);
- }
- previousDictationPhraseStart = dictationPhraseEnd;
- }
-
- RefPtr<Range> resultRange = Range::create(document(), textNode, 0, textNode, textNode->length());
- document().markers().addDictationResultMarker(resultRange.get(), metadata);
-
- client()->respondToChangedContents();
-}
-#endif
-
void Editor::pasteAsPlainText(const String& pastingText, bool smartReplace)
{
Node* target = findEventTargetFromSelection();
if (!target)
return;
- target->dispatchEvent(TextEvent::createForPlainTextPaste(document().domWindow(), pastingText, smartReplace), IGNORE_EXCEPTION);
+ target->dispatchEvent(TextEvent::createForPlainTextPaste(document().domWindow(), pastingText, smartReplace));
}
-void Editor::pasteAsFragment(PassRefPtr<DocumentFragment> pastingFragment, bool smartReplace, bool matchStyle)
+void Editor::pasteAsFragment(Ref<DocumentFragment>&& pastingFragment, bool smartReplace, bool matchStyle, MailBlockquoteHandling respectsMailBlockquote)
{
Node* target = findEventTargetFromSelection();
if (!target)
return;
- target->dispatchEvent(TextEvent::createForFragmentPaste(document().domWindow(), pastingFragment, smartReplace, matchStyle), IGNORE_EXCEPTION);
+ target->dispatchEvent(TextEvent::createForFragmentPaste(document().domWindow(), WTFMove(pastingFragment), smartReplace, matchStyle, respectsMailBlockquote));
}
void Editor::pasteAsPlainTextBypassingDHTML()
@@ -543,7 +446,7 @@ void Editor::pasteAsPlainTextBypassingDHTML()
void Editor::pasteAsPlainTextWithPasteboard(Pasteboard& pasteboard)
{
String text = readPlainTextFromPasteboard(pasteboard);
- if (client() && client()->shouldInsertText(text, selectedRange().get(), EditorInsertActionPasted))
+ if (client() && client()->shouldInsertText(text, selectedRange().get(), EditorInsertAction::Pasted))
pasteAsPlainText(text, canSmartReplaceWithPasteboard(pasteboard));
}
@@ -554,7 +457,7 @@ String Editor::readPlainTextFromPasteboard(Pasteboard& pasteboard)
return plainTextFromPasteboard(text);
}
-#if !(PLATFORM(MAC) && !PLATFORM(IOS))
+#if !PLATFORM(MAC)
String Editor::plainTextFromPasteboard(const PasteboardPlainText& text)
{
@@ -563,20 +466,6 @@ String Editor::plainTextFromPasteboard(const PasteboardPlainText& text)
#endif
-#if !PLATFORM(MAC) && !PLATFORM(EFL)
-void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText)
-{
- RefPtr<Range> range = selectedRange();
- if (!range)
- return;
-
- bool chosePlainText;
- RefPtr<DocumentFragment> fragment = pasteboard->documentFragment(m_frame, *range, allowPlainText, chosePlainText);
- if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
- pasteAsFragment(fragment, canSmartReplaceWithPasteboard(*pasteboard), chosePlainText);
-}
-#endif
-
bool Editor::canSmartReplaceWithPasteboard(Pasteboard& pasteboard)
{
return client() && client()->smartInsertDeleteEnabled() && pasteboard.canSmartReplace();
@@ -589,18 +478,23 @@ bool Editor::shouldInsertFragment(PassRefPtr<DocumentFragment> fragment, PassRef
if (fragment) {
Node* child = fragment->firstChild();
- if (child && fragment->lastChild() == child && child->isCharacterDataNode())
- return client()->shouldInsertText(toCharacterData(child)->data(), replacingDOMRange.get(), givenAction);
+ if (is<CharacterData>(child) && fragment->lastChild() == child)
+ return client()->shouldInsertText(downcast<CharacterData>(*child).data(), replacingDOMRange.get(), givenAction);
}
return client()->shouldInsertNode(fragment.get(), replacingDOMRange.get(), givenAction);
}
-void Editor::replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment, bool selectReplacement, bool smartReplace, bool matchStyle)
+void Editor::replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment, bool selectReplacement, bool smartReplace, bool matchStyle, EditAction editingAction, MailBlockquoteHandling mailBlockquoteHandling)
{
- if (m_frame.selection().isNone() || !m_frame.selection().isContentEditable() || !fragment)
+ VisibleSelection selection = m_frame.selection().selection();
+ if (selection.isNone() || !selection.isContentEditable() || !fragment)
return;
+ AccessibilityReplacedText replacedText;
+ if (AXObjectCache::accessibilityEnabled() && editingAction == EditActionPaste)
+ replacedText = AccessibilityReplacedText(selection);
+
ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::PreventNesting | ReplaceSelectionCommand::SanitizeFragment;
if (selectReplacement)
options |= ReplaceSelectionCommand::SelectReplacement;
@@ -608,116 +502,51 @@ void Editor::replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment,
options |= ReplaceSelectionCommand::SmartReplace;
if (matchStyle)
options |= ReplaceSelectionCommand::MatchStyle;
- applyCommand(ReplaceSelectionCommand::create(document(), fragment, options, EditActionPaste));
+ if (mailBlockquoteHandling == MailBlockquoteHandling::IgnoreBlockquote)
+ options |= ReplaceSelectionCommand::IgnoreMailBlockquote;
+
+ RefPtr<ReplaceSelectionCommand> command = ReplaceSelectionCommand::create(document(), fragment, options, editingAction);
+ applyCommand(command);
revealSelectionAfterEditingOperation();
- if (m_frame.selection().isInPasswordField() || !isContinuousSpellCheckingEnabled())
+ selection = m_frame.selection().selection();
+ if (selection.isInPasswordField())
return;
- Node* nodeToCheck = m_frame.selection().rootEditableElement();
+
+ if (AXObjectCache::accessibilityEnabled() && editingAction == EditActionPaste) {
+ String text = AccessibilityObject::stringForVisiblePositionRange(command->visibleSelectionForInsertedText());
+ replacedText.postTextStateChangeNotification(document().existingAXObjectCache(), AXTextEditTypePaste, text, m_frame.selection().selection());
+ command->composition()->setRangeDeletedByUnapply(replacedText.replacedRange());
+ }
+
+ if (!isContinuousSpellCheckingEnabled())
+ return;
+
+ Node* nodeToCheck = selection.rootEditableElement();
if (!nodeToCheck)
return;
RefPtr<Range> rangeToCheck = Range::create(document(), firstPositionInNode(nodeToCheck), lastPositionInNode(nodeToCheck));
- m_spellChecker->requestCheckingFor(SpellCheckRequest::create(resolveTextCheckingTypeMask(TextCheckingTypeSpelling | TextCheckingTypeGrammar), TextCheckingProcessBatch, rangeToCheck, rangeToCheck));
+ m_spellChecker->requestCheckingFor(SpellCheckRequest::create(resolveTextCheckingTypeMask(*nodeToCheck, TextCheckingTypeSpelling | TextCheckingTypeGrammar), TextCheckingProcessBatch, rangeToCheck, rangeToCheck));
}
-void Editor::replaceSelectionWithText(const String& text, bool selectReplacement, bool smartReplace)
+void Editor::replaceSelectionWithText(const String& text, bool selectReplacement, bool smartReplace, EditAction editingAction)
{
RefPtr<Range> range = selectedRange();
if (!range)
return;
- replaceSelectionWithFragment(createFragmentFromText(*range, text), selectReplacement, smartReplace, true);
+ replaceSelectionWithFragment(createFragmentFromText(*range, text), selectReplacement, smartReplace, true, editingAction);
}
-PassRefPtr<Range> Editor::selectedRange()
+RefPtr<Range> Editor::selectedRange()
{
return m_frame.selection().toNormalizedRange();
}
-#if PLATFORM(IOS)
-void Editor::confirmMarkedText()
-{
- // FIXME: This is a hacky workaround for the keyboard calling this method too late -
- // after the selection and focus have already changed. See <rdar://problem/5975559>
- Element* focused = document().focusedElement();
- Node* composition = compositionNode();
-
- if (composition && focused && focused != composition && !composition->isDescendantOrShadowDescendantOf(focused)) {
- cancelComposition();
- document().setFocusedElement(focused);
- } else
- confirmComposition();
-}
-
-void Editor::setTextAsChildOfElement(const String& text, Element* elem)
-{
- // Clear the composition
- clear();
-
- // Clear the Undo stack, since the operations that follow are not Undoable, and will corrupt the stack. Some day
- // we could make them Undoable, and let callers clear the Undo stack explicitly if they wish.
- clearUndoRedoOperations();
-
- // If the element is empty already and we're not adding text, we can early return and avoid clearing/setting
- // a selection at [0, 0] and the expense involved in creation VisiblePositions.
- if (!elem->firstChild() && text.isEmpty())
- return;
-
- // As a side effect this function sets a caret selection after the inserted content. Much of what
- // follows is more expensive if there is a selection, so clear it since it's going to change anyway.
- m_frame.selection().clear();
-
- // clear out all current children of element
- elem->removeChildren();
-
- if (text.length()) {
- // insert new text
- // remove element from tree while doing it
- // FIXME: The element we're inserting into is often the body element. It seems strange to be removing it
- // (even if it is only temporary). ReplaceSelectionCommand doesn't bother doing this when it inserts
- // content, why should we here?
- ExceptionCode ec;
- RefPtr<Node> parent = elem->parentNode();
- RefPtr<Node> siblingAfter = elem->nextSibling();
- if (parent)
- elem->remove(ec);
-
- RefPtr<Range> context = document().createRange();
- context->selectNodeContents(elem, ec);
- RefPtr<DocumentFragment> fragment = createFragmentFromText(*context.get(), text);
- elem->appendChild(fragment, ec);
-
- // restore element to document
- if (parent) {
- if (siblingAfter)
- parent->insertBefore(elem, siblingAfter.get(), ec);
- else
- parent->appendChild(elem, ec);
- }
- }
-
- // set the selection to the end
- VisibleSelection selection;
-
- Position pos = createLegacyEditingPosition(elem, elem->childNodeCount());
-
- VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
- if (visiblePos.isNull())
- return;
-
- selection.setBase(visiblePos);
- selection.setExtent(visiblePos);
-
- m_frame.selection().setSelection(selection);
-
- client()->respondToChangedContents();
-}
-#endif
-
bool Editor::shouldDeleteRange(Range* range) const
{
- if (!range || range->collapsed(IGNORE_EXCEPTION))
+ if (!range || range->collapsed())
return false;
if (!canDeleteRange(range))
@@ -728,45 +557,31 @@ bool Editor::shouldDeleteRange(Range* range) const
bool Editor::tryDHTMLCopy()
{
- if (m_frame.selection().isInPasswordField())
+ if (m_frame.selection().selection().isInPasswordField())
return false;
- return !dispatchCPPEvent(eventNames().copyEvent, ClipboardWritable);
+ return !dispatchCPPEvent(eventNames().copyEvent, DataTransferAccessPolicy::Writable);
}
bool Editor::tryDHTMLCut()
{
- if (m_frame.selection().isInPasswordField())
+ if (m_frame.selection().selection().isInPasswordField())
return false;
- return !dispatchCPPEvent(eventNames().cutEvent, ClipboardWritable);
+ return !dispatchCPPEvent(eventNames().cutEvent, DataTransferAccessPolicy::Writable);
}
bool Editor::tryDHTMLPaste()
{
- return !dispatchCPPEvent(eventNames().pasteEvent, ClipboardReadable);
+ return !dispatchCPPEvent(eventNames().pasteEvent, DataTransferAccessPolicy::Readable);
}
bool Editor::shouldInsertText(const String& text, Range* range, EditorInsertAction action) const
{
- return client() && client()->shouldInsertText(text, range, action);
-}
-
-void Editor::notifyComponentsOnChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options)
-{
-#if PLATFORM(IOS)
- // FIXME: Merge this to open source https://bugs.webkit.org/show_bug.cgi?id=38830
- if (m_ignoreCompositionSelectionChange)
- return;
-#endif
+ if (m_frame.mainFrame().loader().shouldSuppressKeyboardInput() && action == EditorInsertAction::Typed)
+ return false;
- if (client())
- client()->respondToChangedSelection(&m_frame);
- setStartNewKillRingSequence(true);
-#if ENABLE(DELETION_UI)
- m_deleteButtonController->respondToChangedSelection(oldSelection);
-#endif
- m_alternativeTextController->respondToChangedSelection(oldSelection, options);
+ return client() && client()->shouldInsertText(text, range, action);
}
void Editor::respondToChangedContents(const VisibleSelection& endingSelection)
@@ -797,8 +612,11 @@ bool Editor::hasBidiSelection() const
} else
startNode = m_frame.selection().selection().visibleStart().deepEquivalent().deprecatedNode();
+ if (!startNode)
+ return false;
+
auto renderer = startNode->renderer();
- while (renderer && !renderer->isRenderBlockFlow())
+ while (renderer && !is<RenderBlockFlow>(*renderer))
renderer = renderer->parent();
if (!renderer)
@@ -807,17 +625,17 @@ bool Editor::hasBidiSelection() const
if (!renderer->style().isLeftToRightDirection())
return true;
- return toRenderBlockFlow(renderer)->containsNonZeroBidiLevel();
+ return downcast<RenderBlockFlow>(*renderer).containsNonZeroBidiLevel();
}
TriState Editor::selectionUnorderedListState() const
{
if (m_frame.selection().isCaret()) {
- if (enclosingNodeWithTag(m_frame.selection().selection().start(), ulTag))
+ if (enclosingElementWithTag(m_frame.selection().selection().start(), ulTag))
return TrueTriState;
} else if (m_frame.selection().isRange()) {
- Node* startNode = enclosingNodeWithTag(m_frame.selection().selection().start(), ulTag);
- Node* endNode = enclosingNodeWithTag(m_frame.selection().selection().end(), ulTag);
+ auto* startNode = enclosingElementWithTag(m_frame.selection().selection().start(), ulTag);
+ auto* endNode = enclosingElementWithTag(m_frame.selection().selection().end(), ulTag);
if (startNode && endNode && startNode == endNode)
return TrueTriState;
}
@@ -828,11 +646,11 @@ TriState Editor::selectionUnorderedListState() const
TriState Editor::selectionOrderedListState() const
{
if (m_frame.selection().isCaret()) {
- if (enclosingNodeWithTag(m_frame.selection().selection().start(), olTag))
+ if (enclosingElementWithTag(m_frame.selection().selection().start(), olTag))
return TrueTriState;
} else if (m_frame.selection().isRange()) {
- Node* startNode = enclosingNodeWithTag(m_frame.selection().selection().start(), olTag);
- Node* endNode = enclosingNodeWithTag(m_frame.selection().selection().end(), olTag);
+ auto* startNode = enclosingElementWithTag(m_frame.selection().selection().start(), olTag);
+ auto* endNode = enclosingElementWithTag(m_frame.selection().selection().end(), olTag);
if (startNode && endNode && startNode == endNode)
return TrueTriState;
}
@@ -870,34 +688,34 @@ bool Editor::canDecreaseSelectionListLevel()
return canEditRichly() && DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(&document());
}
-PassRefPtr<Node> Editor::increaseSelectionListLevel()
+RefPtr<Node> Editor::increaseSelectionListLevel()
{
if (!canEditRichly() || m_frame.selection().isNone())
- return 0;
+ return nullptr;
RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevel(&document());
revealSelectionAfterEditingOperation();
return newList;
}
-PassRefPtr<Node> Editor::increaseSelectionListLevelOrdered()
+RefPtr<Node> Editor::increaseSelectionListLevelOrdered()
{
if (!canEditRichly() || m_frame.selection().isNone())
- return 0;
+ return nullptr;
RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(&document());
revealSelectionAfterEditingOperation();
- return newList.release();
+ return newList;
}
-PassRefPtr<Node> Editor::increaseSelectionListLevelUnordered()
+RefPtr<Node> Editor::increaseSelectionListLevelUnordered()
{
if (!canEditRichly() || m_frame.selection().isNone())
- return 0;
+ return nullptr;
RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(&document());
revealSelectionAfterEditingOperation();
- return newList.release();
+ return newList;
}
void Editor::decreaseSelectionListLevel()
@@ -916,38 +734,34 @@ void Editor::removeFormattingAndStyle()
void Editor::clearLastEditCommand()
{
- m_lastEditCommand.clear();
+ m_lastEditCommand = nullptr;
}
-#if PLATFORM(IOS)
-// If the selection is adjusted from UIKit without closing the typing, the typing command may
-// have a stale selection.
-void Editor::ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping()
-{
- TypingCommand::ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping(&m_frame, m_frame.selection().selection());
-}
-#endif
// Returns whether caller should continue with "the default processing", which is the same as
// the event handler NOT setting the return value to false
-bool Editor::dispatchCPPEvent(const AtomicString& eventType, ClipboardAccessPolicy policy)
+bool Editor::dispatchCPPEvent(const AtomicString& eventType, DataTransferAccessPolicy policy)
{
Node* target = findEventTargetFromSelection();
if (!target)
return true;
- RefPtr<Clipboard> clipboard = Clipboard::createForCopyAndPaste(policy);
+ auto dataTransfer = DataTransfer::createForCopyAndPaste(policy);
- RefPtr<Event> event = ClipboardEvent::create(eventType, true, true, clipboard);
- target->dispatchEvent(event, IGNORE_EXCEPTION);
+ ClipboardEvent::Init init;
+ init.bubbles = true;
+ init.cancelable = true;
+ init.clipboardData = dataTransfer.ptr();
+ auto event = ClipboardEvent::create(eventType, init, Event::IsTrusted::Yes);
+ target->dispatchEvent(event);
bool noDefaultProcessing = event->defaultPrevented();
- if (noDefaultProcessing && policy == ClipboardWritable) {
- OwnPtr<Pasteboard> pasteboard = Pasteboard::createForCopyAndPaste();
+ if (noDefaultProcessing && policy == DataTransferAccessPolicy::Writable) {
+ auto pasteboard = Pasteboard::createForCopyAndPaste();
pasteboard->clear();
- pasteboard->writePasteboard(clipboard->pasteboard());
+ pasteboard->writePasteboard(dataTransfer->pasteboard());
}
- // invalidate clipboard here for security
- clipboard->setAccessPolicy(ClipboardNumb);
+ // invalidate dataTransfer here for security
+ dataTransfer->setAccessPolicy(DataTransferAccessPolicy::Numb);
return !noDefaultProcessing;
}
@@ -956,9 +770,9 @@ Node* Editor::findEventTargetFrom(const VisibleSelection& selection) const
{
Node* target = selection.start().element();
if (!target)
- target = document().body();
+ target = document().bodyOrFrameset();
if (!target)
- return 0;
+ return nullptr;
return target;
}
@@ -970,18 +784,39 @@ Node* Editor::findEventTargetFromSelection() const
void Editor::applyStyle(StyleProperties* style, EditAction editingAction)
{
- switch (m_frame.selection().selectionType()) {
- case VisibleSelection::NoSelection:
- // do nothing
- break;
+ if (style)
+ applyStyle(EditingStyle::create(style), editingAction);
+}
+
+void Editor::applyStyle(RefPtr<EditingStyle>&& style, EditAction editingAction)
+{
+ if (!style)
+ return;
+
+ auto selectionType = m_frame.selection().selection().selectionType();
+ if (selectionType == VisibleSelection::NoSelection)
+ return;
+
+ String inputTypeName = inputTypeNameForEditingAction(editingAction);
+ String inputEventData = inputEventDataForEditingStyleAndAction(*style, editingAction);
+ RefPtr<Element> element = m_frame.selection().selection().rootEditableElement();
+ if (element && !dispatchBeforeInputEvent(*element, inputTypeName, inputEventData))
+ return;
+
+ switch (selectionType) {
case VisibleSelection::CaretSelection:
- computeAndSetTypingStyle(style, editingAction);
+ computeAndSetTypingStyle(*style, editingAction);
break;
case VisibleSelection::RangeSelection:
- if (style)
- applyCommand(ApplyStyleCommand::create(document(), EditingStyle::create(style).get(), editingAction));
+ applyCommand(ApplyStyleCommand::create(document(), style.get(), editingAction));
+ break;
+ default:
break;
}
+
+ client()->didApplyStyle();
+ if (element)
+ dispatchInputEvent(*element, inputTypeName, inputEventData);
}
bool Editor::shouldApplyStyle(StyleProperties* style, Range* range)
@@ -991,16 +826,23 @@ bool Editor::shouldApplyStyle(StyleProperties* style, Range* range)
void Editor::applyParagraphStyle(StyleProperties* style, EditAction editingAction)
{
- switch (m_frame.selection().selectionType()) {
- case VisibleSelection::NoSelection:
- // do nothing
- break;
- case VisibleSelection::CaretSelection:
- case VisibleSelection::RangeSelection:
- if (style)
- applyCommand(ApplyStyleCommand::create(document(), EditingStyle::create(style).get(), editingAction, ApplyStyleCommand::ForceBlockProperties));
- break;
- }
+ if (!style)
+ return;
+
+ auto selectionType = m_frame.selection().selection().selectionType();
+ if (selectionType == VisibleSelection::NoSelection)
+ return;
+
+ String inputTypeName = inputTypeNameForEditingAction(editingAction);
+ String inputEventData = inputEventDataForEditingStyleAndAction(style, editingAction);
+ RefPtr<Element> element = m_frame.selection().selection().rootEditableElement();
+ if (element && !dispatchBeforeInputEvent(*element, inputTypeName, inputEventData))
+ return;
+
+ applyCommand(ApplyStyleCommand::create(document(), EditingStyle::create(style).ptr(), editingAction, ApplyStyleCommand::ForceBlockProperties));
+ client()->didApplyStyle();
+ if (element)
+ dispatchInputEvent(*element, inputTypeName, inputEventData);
}
void Editor::applyStyleToSelection(StyleProperties* style, EditAction editingAction)
@@ -1008,8 +850,21 @@ void Editor::applyStyleToSelection(StyleProperties* style, EditAction editingAct
if (!style || style->isEmpty() || !canEditRichly())
return;
- if (client() && client()->shouldApplyStyle(style, m_frame.selection().toNormalizedRange().get()))
- applyStyle(style, editingAction);
+ if (!client() || !client()->shouldApplyStyle(style, m_frame.selection().toNormalizedRange().get()))
+ return;
+ applyStyle(style, editingAction);
+}
+
+void Editor::applyStyleToSelection(Ref<EditingStyle>&& style, EditAction editingAction)
+{
+ if (style->isEmpty() || !canEditRichly())
+ return;
+
+ // FIXME: This is wrong for text decorations since m_mutableStyle is empty.
+ if (!client() || !client()->shouldApplyStyle(style->styleWithResolvedTextDecorations().ptr(), m_frame.selection().toNormalizedRange().get()))
+ return;
+
+ applyStyle(WTFMove(style), editingAction);
}
void Editor::applyParagraphStyleToSelection(StyleProperties* style, EditAction editingAction)
@@ -1054,93 +909,147 @@ void Editor::outdent()
applyCommand(IndentOutdentCommand::create(document(), IndentOutdentCommand::Outdent));
}
-static void dispatchEditableContentChangedEvents(PassRefPtr<Element> prpStartRoot, PassRefPtr<Element> prpEndRoot)
+static void notifyTextFromControls(Element* startRoot, Element* endRoot)
+{
+ HTMLTextFormControlElement* startingTextControl = enclosingTextFormControl(firstPositionInOrBeforeNode(startRoot));
+ HTMLTextFormControlElement* endingTextControl = enclosingTextFormControl(firstPositionInOrBeforeNode(endRoot));
+ if (startingTextControl)
+ startingTextControl->didEditInnerTextValue();
+ if (endingTextControl && startingTextControl != endingTextControl)
+ endingTextControl->didEditInnerTextValue();
+}
+
+static bool dispatchBeforeInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomicString& inputTypeName, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }, bool cancelable = true)
{
- RefPtr<Element> startRoot = prpStartRoot;
- RefPtr<Element> endRoot = prpEndRoot;
+ bool continueWithDefaultBehavior = true;
if (startRoot)
- startRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false), IGNORE_EXCEPTION);
+ continueWithDefaultBehavior &= dispatchBeforeInputEvent(*startRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges, cancelable);
if (endRoot && endRoot != startRoot)
- endRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false), IGNORE_EXCEPTION);
+ continueWithDefaultBehavior &= dispatchBeforeInputEvent(*endRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges, cancelable);
+ return continueWithDefaultBehavior;
+}
+
+static void dispatchInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomicString& inputTypeName, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { })
+{
+ if (startRoot)
+ dispatchInputEvent(*startRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges);
+ if (endRoot && endRoot != startRoot)
+ dispatchInputEvent(*endRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges);
+}
+
+bool Editor::willApplyEditing(CompositeEditCommand& command, Vector<RefPtr<StaticRange>>&& targetRanges) const
+{
+ if (!command.shouldDispatchInputEvents())
+ return true;
+
+ auto* composition = command.composition();
+ if (!composition)
+ return true;
+
+ return dispatchBeforeInputEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement(), command.inputEventTypeName(), command.inputEventData(), command.inputEventDataTransfer(), targetRanges, command.isBeforeInputEventCancelable());
}
void Editor::appliedEditing(PassRefPtr<CompositeEditCommand> cmd)
{
+ LOG(Editing, "Editor %p appliedEditing", this);
+
document().updateLayout();
EditCommandComposition* composition = cmd->composition();
ASSERT(composition);
VisibleSelection newSelection(cmd->endingSelection());
- m_alternativeTextController->respondToAppliedEditing(cmd.get());
+ notifyTextFromControls(composition->startingRootEditableElement(), composition->endingRootEditableElement());
- // Don't clear the typing style with this selection change. We do those things elsewhere if necessary.
- FrameSelection::SetSelectionOptions options = cmd->isDictationCommand() ? FrameSelection::DictationTriggered : 0;
- changeSelectionAfterCommand(newSelection, options);
- dispatchEditableContentChangedEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement());
+ if (cmd->isTopLevelCommand()) {
+ // Don't clear the typing style with this selection change. We do those things elsewhere if necessary.
+ FrameSelection::SetSelectionOptions options = cmd->isDictationCommand() ? FrameSelection::DictationTriggered : 0;
- if (!cmd->preservesTypingStyle())
- m_frame.selection().clearTypingStyle();
+ changeSelectionAfterCommand(newSelection, options);
+ }
- // Command will be equal to last edit command only in the case of typing
- if (m_lastEditCommand.get() == cmd)
- ASSERT(cmd->isTypingCommand());
- else {
- // Only register a new undo command if the command passed in is
- // different from the last command
- m_lastEditCommand = cmd;
- if (client())
- client()->registerUndoStep(m_lastEditCommand->ensureComposition());
+ if (cmd->shouldDispatchInputEvents())
+ dispatchInputEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement(), cmd->inputEventTypeName(), cmd->inputEventData(), cmd->inputEventDataTransfer());
+
+ if (cmd->isTopLevelCommand()) {
+ updateEditorUINowIfScheduled();
+
+ m_alternativeTextController->respondToAppliedEditing(cmd.get());
+
+ if (!cmd->preservesTypingStyle())
+ m_frame.selection().clearTypingStyle();
+
+ // Command will be equal to last edit command only in the case of typing
+ if (m_lastEditCommand.get() == cmd)
+ ASSERT(cmd->isTypingCommand());
+ else {
+ // Only register a new undo command if the command passed in is
+ // different from the last command
+ m_lastEditCommand = cmd;
+ if (client())
+ client()->registerUndoStep(m_lastEditCommand->ensureComposition());
+ }
+ respondToChangedContents(newSelection);
}
+}
- respondToChangedContents(newSelection);
+bool Editor::willUnapplyEditing(const EditCommandComposition& composition) const
+{
+ return dispatchBeforeInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyUndo");
}
-void Editor::unappliedEditing(PassRefPtr<EditCommandComposition> cmd)
+void Editor::unappliedEditing(EditCommandComposition& composition)
{
document().updateLayout();
- VisibleSelection newSelection(cmd->startingSelection());
- changeSelectionAfterCommand(newSelection, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle);
- dispatchEditableContentChangedEvents(cmd->startingRootEditableElement(), cmd->endingRootEditableElement());
+ notifyTextFromControls(composition.startingRootEditableElement(), composition.endingRootEditableElement());
- m_alternativeTextController->respondToUnappliedEditing(cmd.get());
+ VisibleSelection newSelection(composition.startingSelection());
+ changeSelectionAfterCommand(newSelection, FrameSelection::defaultSetSelectionOptions());
+ dispatchInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyUndo");
- m_lastEditCommand = 0;
- if (client())
- client()->registerRedoStep(cmd);
+ updateEditorUINowIfScheduled();
+
+ m_alternativeTextController->respondToUnappliedEditing(&composition);
+
+ m_lastEditCommand = nullptr;
+ if (auto* client = this->client())
+ client->registerRedoStep(composition);
respondToChangedContents(newSelection);
}
-void Editor::reappliedEditing(PassRefPtr<EditCommandComposition> cmd)
+bool Editor::willReapplyEditing(const EditCommandComposition& composition) const
+{
+ return dispatchBeforeInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyRedo");
+}
+
+void Editor::reappliedEditing(EditCommandComposition& composition)
{
document().updateLayout();
- VisibleSelection newSelection(cmd->endingSelection());
- changeSelectionAfterCommand(newSelection, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle);
- dispatchEditableContentChangedEvents(cmd->startingRootEditableElement(), cmd->endingRootEditableElement());
+ notifyTextFromControls(composition.startingRootEditableElement(), composition.endingRootEditableElement());
- m_lastEditCommand = 0;
- if (client())
- client()->registerUndoStep(cmd);
+ VisibleSelection newSelection(composition.endingSelection());
+ changeSelectionAfterCommand(newSelection, FrameSelection::defaultSetSelectionOptions());
+ dispatchInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyRedo");
+
+ updateEditorUINowIfScheduled();
+
+ m_lastEditCommand = nullptr;
+ if (auto* client = this->client())
+ client->registerUndoStep(composition);
respondToChangedContents(newSelection);
}
Editor::Editor(Frame& frame)
: m_frame(frame)
-#if ENABLE(DELETION_UI)
- , m_deleteButtonController(adoptPtr(new DeleteButtonController(frame)))
+ , m_killRing(std::make_unique<KillRing>())
+ , m_spellChecker(std::make_unique<SpellChecker>(frame))
+ , m_alternativeTextController(std::make_unique<AlternativeTextController>(frame))
+ , m_editorUIUpdateTimer(*this, &Editor::editorUIUpdateTimerFired)
+#if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS)
+ , m_telephoneNumberDetectionUpdateTimer(*this, &Editor::scanSelectionForTelephoneNumbers)
#endif
- , m_ignoreCompositionSelectionChange(false)
- , m_shouldStartNewKillRingSequence(false)
- // This is off by default, since most editors want this behavior (this matches IE but not FF).
- , m_shouldStyleWithCSS(false)
- , m_killRing(adoptPtr(new KillRing))
- , m_spellChecker(adoptPtr(new SpellChecker(frame)))
- , m_alternativeTextController(adoptPtr(new AlternativeTextController(frame)))
- , m_areMarkedTextMatchesHighlighted(false)
- , m_defaultParagraphSeparator(EditorParagraphSeparatorIsDiv)
- , m_overwriteModeEnabled(false)
{
}
@@ -1150,19 +1059,19 @@ Editor::~Editor()
void Editor::clear()
{
- m_compositionNode = 0;
+ if (m_compositionNode) {
+ m_compositionNode = nullptr;
+ if (EditorClient* client = this->client())
+ client->discardedComposition(&m_frame);
+ }
m_customCompositionUnderlines.clear();
m_shouldStyleWithCSS = false;
m_defaultParagraphSeparator = EditorParagraphSeparatorIsDiv;
-
-#if ENABLE(DELETION_UI)
- m_deleteButtonController = adoptPtr(new DeleteButtonController(m_frame));
-#endif
}
-bool Editor::insertText(const String& text, Event* triggeringEvent)
+bool Editor::insertText(const String& text, Event* triggeringEvent, TextEventInputType inputType)
{
- return m_frame.eventHandler().handleTextInputEvent(text, triggeringEvent);
+ return m_frame.eventHandler().handleTextInputEvent(text, triggeringEvent, inputType);
}
bool Editor::insertTextForConfirmedComposition(const String& text)
@@ -1185,7 +1094,7 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn
return false;
RefPtr<Range> range = selection.toNormalizedRange();
- if (!shouldInsertText(text, range.get(), EditorInsertActionTyped))
+ if (!shouldInsertText(text, range.get(), EditorInsertAction::Typed))
return true;
updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0]));
@@ -1209,20 +1118,28 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn
// Insert the text
if (triggeringEvent && triggeringEvent->isDictation())
- DictationCommand::insertText(&document.get(), text, triggeringEvent->dictationAlternatives(), selection);
+ DictationCommand::insertText(document, text, triggeringEvent->dictationAlternatives(), selection);
else {
TypingCommand::Options options = 0;
if (selectInsertedText)
options |= TypingCommand::SelectInsertedText;
if (autocorrectionWasApplied)
options |= TypingCommand::RetainAutocorrectionIndicator;
- TypingCommand::insertText(document.get(), text, selection, options, triggeringEvent && triggeringEvent->isComposition() ? TypingCommand::TextCompositionConfirm : TypingCommand::TextCompositionNone);
+ if (triggeringEvent && triggeringEvent->isAutocompletion())
+ options |= TypingCommand::IsAutocompletion;
+ TypingCommand::insertText(document, text, selection, options, triggeringEvent && triggeringEvent->isComposition() ? TypingCommand::TextCompositionFinal : TypingCommand::TextCompositionNone);
}
// Reveal the current selection
if (Frame* editedFrame = document->frame())
- if (Page* page = editedFrame->page())
- page->focusController().focusedOrMainFrame().selection().revealSelection(ScrollAlignment::alignCenterIfNeeded);
+ if (Page* page = editedFrame->page()) {
+#if PLATFORM(IOS)
+ SelectionRevealMode revealMode = SelectionRevealMode::RevealUpToMainFrame;
+#else
+ SelectionRevealMode revealMode = SelectionRevealMode::Reveal;
+#endif
+ page->focusController().focusedOrMainFrame().selection().revealSelection(revealMode, ScrollAlignment::alignCenterIfNeeded);
+ }
}
}
@@ -1234,7 +1151,7 @@ bool Editor::insertLineBreak()
if (!canEdit())
return false;
- if (!shouldInsertText("\n", m_frame.selection().toNormalizedRange().get(), EditorInsertActionTyped))
+ if (!shouldInsertText("\n", m_frame.selection().toNormalizedRange().get(), EditorInsertAction::Typed))
return true;
VisiblePosition caret = m_frame.selection().selection().visibleStart();
@@ -1254,7 +1171,7 @@ bool Editor::insertParagraphSeparator()
if (!canEditRichly())
return insertLineBreak();
- if (!shouldInsertText("\n", m_frame.selection().toNormalizedRange().get(), EditorInsertActionTyped))
+ if (!shouldInsertText("\n", m_frame.selection().toNormalizedRange().get(), EditorInsertAction::Typed))
return true;
VisiblePosition caret = m_frame.selection().selection().visibleStart();
@@ -1266,6 +1183,14 @@ bool Editor::insertParagraphSeparator()
return true;
}
+bool Editor::insertParagraphSeparatorInQuotedContent()
+{
+ // FIXME: Why is this missing calls to canEdit, canEditRichly, etc.?
+ TypingCommand::insertParagraphSeparatorInQuotedContent(document());
+ revealSelectionAfterEditingOperation();
+ return true;
+}
+
void Editor::cut()
{
if (tryDHTMLCut())
@@ -1275,24 +1200,7 @@ void Editor::cut()
return;
}
- // FIXME: This should share more code with the copy function; there is a lot of overlap.
- RefPtr<Range> selection = selectedRange();
- willWriteSelectionToPasteboard(selection);
- if (shouldDeleteRange(selection.get())) {
- updateMarkersForWordsAffectedByEditing(true);
- if (enclosingTextFormControl(m_frame.selection().start()))
- Pasteboard::createForCopyAndPaste()->writePlainText(selectedTextForClipboard(), canSmartCopyOrDelete() ? Pasteboard::CanSmartReplace : Pasteboard::CannotSmartReplace);
- else {
-#if PLATFORM(MAC) || PLATFORM(EFL)
- writeSelectionToPasteboard(*Pasteboard::createForCopyAndPaste());
-#else
- // FIXME: Convert all other platforms to match Mac and delete this.
- Pasteboard::createForCopyAndPaste()->writeSelection(*selection, canSmartCopyOrDelete(), m_frame, IncludeImageAltTextForClipboard);
-#endif
- }
- didWriteSelectionToPasteboard();
- deleteSelectionWithSmartDelete(canSmartCopyOrDelete());
- }
+ performCutOrCopy(CutAction);
}
void Editor::copy()
@@ -1304,28 +1212,64 @@ void Editor::copy()
return;
}
- willWriteSelectionToPasteboard(selectedRange());
- if (enclosingTextFormControl(m_frame.selection().start())) {
- Pasteboard::createForCopyAndPaste()->writePlainText(selectedTextForClipboard(),
- canSmartCopyOrDelete() ? Pasteboard::CanSmartReplace : Pasteboard::CannotSmartReplace);
- } else {
- if (HTMLImageElement* imageElement = imageElementFromImageDocument(document())) {
-#if PLATFORM(MAC) || PLATFORM(EFL)
+ performCutOrCopy(CopyAction);
+}
+
+void Editor::postTextStateChangeNotificationForCut(const String& text, const VisibleSelection& selection)
+{
+ if (!AXObjectCache::accessibilityEnabled())
+ return;
+ if (!text.length())
+ return;
+ AXObjectCache* cache = document().existingAXObjectCache();
+ if (!cache)
+ return;
+ cache->postTextStateChangeNotification(selection.start().anchorNode(), AXTextEditTypeCut, text, selection.start());
+}
+
+void Editor::performCutOrCopy(EditorActionSpecifier action)
+{
+ RefPtr<Range> selection = selectedRange();
+ willWriteSelectionToPasteboard(selection);
+ if (action == CutAction) {
+ if (!shouldDeleteRange(selection.get()))
+ return;
+
+ updateMarkersForWordsAffectedByEditing(true);
+ }
+
+ if (enclosingTextFormControl(m_frame.selection().selection().start()))
+ Pasteboard::createForCopyAndPaste()->writePlainText(selectedTextForDataTransfer(), canSmartCopyOrDelete() ? Pasteboard::CanSmartReplace : Pasteboard::CannotSmartReplace);
+ else {
+ HTMLImageElement* imageElement = nullptr;
+ if (action == CopyAction)
+ imageElement = imageElementFromImageDocument(document());
+
+ if (imageElement) {
+#if PLATFORM(COCOA) || PLATFORM(GTK)
writeImageToPasteboard(*Pasteboard::createForCopyAndPaste(), *imageElement, document().url(), document().title());
#else
Pasteboard::createForCopyAndPaste()->writeImage(*imageElement, document().url(), document().title());
#endif
} else {
-#if PLATFORM(MAC) || PLATFORM(EFL)
+#if PLATFORM(COCOA) || PLATFORM(GTK)
writeSelectionToPasteboard(*Pasteboard::createForCopyAndPaste());
#else
// FIXME: Convert all other platforms to match Mac and delete this.
- Pasteboard::createForCopyAndPaste()->writeSelection(*selectedRange(), canSmartCopyOrDelete(), m_frame, IncludeImageAltTextForClipboard);
+ Pasteboard::createForCopyAndPaste()->writeSelection(*selection, canSmartCopyOrDelete(), m_frame, IncludeImageAltTextForDataTransfer);
#endif
}
}
didWriteSelectionToPasteboard();
+ if (action == CutAction) {
+ String text;
+ if (AXObjectCache::accessibilityEnabled())
+ text = AccessibilityObject::stringForVisiblePositionRange(m_frame.selection().selection());
+ deleteSelectionWithSmartDelete(canSmartCopyOrDelete(), EditActionCut);
+ if (AXObjectCache::accessibilityEnabled())
+ postTextStateChangeNotificationForCut(text, m_frame.selection().selection());
+ }
}
void Editor::paste()
@@ -1340,9 +1284,8 @@ void Editor::paste(Pasteboard& pasteboard)
if (!canPaste())
return;
updateMarkersForWordsAffectedByEditing(false);
- CachedResourceLoader* loader = document().cachedResourceLoader();
- ResourceCacheValidationSuppressor validationSuppressor(loader);
- if (m_frame.selection().isContentRichlyEditable())
+ ResourceCacheValidationSuppressor validationSuppressor(document().cachedResourceLoader());
+ if (m_frame.selection().selection().isContentRichlyEditable())
pasteWithPasteboard(&pasteboard, true);
else
pasteAsPlainTextWithPasteboard(pasteboard);
@@ -1365,7 +1308,7 @@ void Editor::performDelete()
return;
}
- addToKillRing(selectedRange().get(), false);
+ addRangeToKillRing(*selectedRange().get(), KillRingInsertionMode::AppendText);
deleteSelectionWithSmartDelete(canSmartCopyOrDelete());
// clear the "start new kill ring sequence" setting, because it was set to true
@@ -1383,12 +1326,12 @@ void Editor::simplifyMarkup(Node* startNode, Node* endNode)
// check if start node is before endNode
Node* node = startNode;
while (node && node != endNode)
- node = NodeTraversal::next(node);
+ node = NodeTraversal::next(*node);
if (!node)
return;
}
- applyCommand(SimplifyMarkupCommand::create(document(), startNode, (endNode) ? NodeTraversal::next(endNode) : 0));
+ applyCommand(SimplifyMarkupCommand::create(document(), startNode, endNode ? NodeTraversal::next(*endNode) : nullptr));
}
void Editor::copyURL(const URL& url, const String& title)
@@ -1402,7 +1345,7 @@ void Editor::copyURL(const URL& url, const String& title, Pasteboard& pasteboard
pasteboardURL.url = url;
pasteboardURL.title = title;
-#if PLATFORM(MAC) && !PLATFORM(IOS)
+#if PLATFORM(MAC)
fillInUserVisibleForm(pasteboardURL);
#endif
@@ -1410,6 +1353,7 @@ void Editor::copyURL(const URL& url, const String& title, Pasteboard& pasteboard
}
#if !PLATFORM(IOS)
+
void Editor::copyImage(const HitTestResult& result)
{
Element* element = result.innerNonSharedElement();
@@ -1420,12 +1364,13 @@ void Editor::copyImage(const HitTestResult& result)
if (url.isEmpty())
url = result.absoluteImageURL();
-#if PLATFORM(MAC) || PLATFORM(EFL)
+#if PLATFORM(COCOA) || PLATFORM(GTK)
writeImageToPasteboard(*Pasteboard::createForCopyAndPaste(), *element, url, result.altDisplayString());
#else
Pasteboard::createForCopyAndPaste()->writeImage(*element, url, result.altDisplayString());
#endif
}
+
#endif
bool Editor::isContinuousSpellCheckingEnabled() const
@@ -1644,11 +1589,18 @@ void Editor::setBaseWritingDirection(WritingDirection direction)
#endif
Element* focusedElement = document().focusedElement();
- if (focusedElement && isHTMLTextFormControlElement(*focusedElement)) {
+ if (is<HTMLTextFormControlElement>(focusedElement)) {
if (direction == NaturalWritingDirection)
return;
- toHTMLElement(focusedElement)->setAttribute(dirAttr, direction == LeftToRightWritingDirection ? "ltr" : "rtl");
- focusedElement->dispatchInputEvent();
+
+ auto& focusedFormElement = downcast<HTMLTextFormControlElement>(*focusedElement);
+ auto directionValue = direction == LeftToRightWritingDirection ? "ltr" : "rtl";
+ auto writingDirectionInputTypeName = inputTypeNameForEditingAction(EditActionSetWritingDirection);
+ if (!dispatchBeforeInputEvent(focusedFormElement, writingDirectionInputTypeName, directionValue))
+ return;
+
+ focusedFormElement.setAttributeWithoutSynchronization(dirAttr, directionValue);
+ dispatchInputEvent(focusedFormElement, writingDirectionInputTypeName, directionValue);
document().updateStyleIfNeeded();
return;
}
@@ -1742,29 +1694,25 @@ void Editor::setComposition(const String& text, SetCompositionMode mode)
else
selectComposition();
+ m_compositionNode = nullptr;
+ m_customCompositionUnderlines.clear();
+
if (m_frame.selection().isNone()) {
setIgnoreCompositionSelectionChange(false);
return;
}
-
- // Dispatch a compositionend event to the focused node.
- // We should send this event before sending a TextEvent as written in Section 6.2.2 and 6.2.3 of
- // the DOM Event specification.
- if (Element* target = document().focusedElement()) {
- RefPtr<CompositionEvent> event = CompositionEvent::create(eventNames().compositionendEvent, document().domWindow(), text);
- target->dispatchEvent(event.release(), IGNORE_EXCEPTION);
- }
-
- // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
- // will delete the old composition with an optimized replace operation.
- if (text.isEmpty() && mode != CancelComposition)
- TypingCommand::deleteSelection(document(), 0);
- m_compositionNode = 0;
- m_customCompositionUnderlines.clear();
+ // Always delete the current composition before inserting the finalized composition text if we're confirming our composition.
+ // Our default behavior (if the beforeinput event is not prevented) is to insert the finalized composition text back in.
+ // We pass TypingCommand::TextCompositionPending here to indicate that we are deleting the pending composition.
+ if (mode != CancelComposition)
+ TypingCommand::deleteSelection(document(), 0, TypingCommand::TextCompositionPending);
insertTextForConfirmedComposition(text);
+ if (auto* target = document().focusedElement())
+ target->dispatchEvent(CompositionEvent::create(eventNames().compositionendEvent, document().domWindow(), text));
+
if (mode == CancelComposition) {
// An open typing command that disagrees about current selection would cause issues with typing later on.
TypingCommand::closeTyping(&m_frame);
@@ -1775,6 +1723,8 @@ void Editor::setComposition(const String& text, SetCompositionMode mode)
void Editor::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd)
{
+ Ref<Frame> protection(m_frame);
+
UserTypingGestureIndicator typingGestureIndicator(m_frame);
setIgnoreCompositionSelectionChange(true);
@@ -1791,6 +1741,18 @@ void Editor::setComposition(const String& text, const Vector<CompositionUnderlin
return;
}
+ String originalText = selectedText();
+ bool isStartingToRecomposeExistingRange = !text.isEmpty() && selectionStart < selectionEnd && !hasComposition();
+ if (isStartingToRecomposeExistingRange) {
+ // We pass TypingCommand::TextCompositionFinal here to indicate that we are removing composition text that has been finalized.
+ TypingCommand::deleteSelection(document(), 0, TypingCommand::TextCompositionFinal);
+ const VisibleSelection& currentSelection = m_frame.selection().selection();
+ if (currentSelection.isRange()) {
+ // If deletion was prevented, then we need to collapse the selection to the end so that the original text will not be recomposed.
+ m_frame.selection().setSelection({ currentSelection.end(), currentSelection.end() });
+ }
+ }
+
#if PLATFORM(IOS)
client()->startDelayingAndCoalescingContentChangeNotifications();
#endif
@@ -1817,47 +1779,46 @@ void Editor::setComposition(const String& text, const Vector<CompositionUnderlin
// We should send a compositionstart event only when the given text is not empty because this
// function doesn't create a composition node when the text is empty.
if (!text.isEmpty()) {
- target->dispatchEvent(CompositionEvent::create(eventNames().compositionstartEvent, document().domWindow(), selectedText()));
+ target->dispatchEvent(CompositionEvent::create(eventNames().compositionstartEvent, document().domWindow(), originalText));
event = CompositionEvent::create(eventNames().compositionupdateEvent, document().domWindow(), text);
}
- } else {
- if (!text.isEmpty())
- event = CompositionEvent::create(eventNames().compositionupdateEvent, document().domWindow(), text);
- else
- event = CompositionEvent::create(eventNames().compositionendEvent, document().domWindow(), text);
- }
- if (event.get())
- target->dispatchEvent(event, IGNORE_EXCEPTION);
+ } else if (!text.isEmpty())
+ event = CompositionEvent::create(eventNames().compositionupdateEvent, document().domWindow(), text);
+
+ if (event)
+ target->dispatchEvent(*event);
}
// If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
// will delete the old composition with an optimized replace operation.
- if (text.isEmpty())
- TypingCommand::deleteSelection(document(), TypingCommand::PreventSpellChecking);
+ if (text.isEmpty()) {
+ TypingCommand::deleteSelection(document(), TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionPending);
+ if (target)
+ target->dispatchEvent(CompositionEvent::create(eventNames().compositionendEvent, document().domWindow(), text));
+ }
- m_compositionNode = 0;
+ m_compositionNode = nullptr;
m_customCompositionUnderlines.clear();
if (!text.isEmpty()) {
- TypingCommand::insertText(document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate);
+ TypingCommand::insertText(document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionPending);
// Find out what node has the composition now.
- Position base = m_frame.selection().base().downstream();
- Position extent = m_frame.selection().extent();
+ Position base = m_frame.selection().selection().base().downstream();
+ Position extent = m_frame.selection().selection().extent();
Node* baseNode = base.deprecatedNode();
unsigned baseOffset = base.deprecatedEditingOffset();
Node* extentNode = extent.deprecatedNode();
unsigned extentOffset = extent.deprecatedEditingOffset();
- if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) {
- m_compositionNode = toText(baseNode);
+ if (is<Text>(baseNode) && baseNode == extentNode && baseOffset + text.length() == extentOffset) {
+ m_compositionNode = downcast<Text>(baseNode);
m_compositionStart = baseOffset;
m_compositionEnd = extentOffset;
m_customCompositionUnderlines = underlines;
- size_t numUnderlines = m_customCompositionUnderlines.size();
- for (size_t i = 0; i < numUnderlines; ++i) {
- m_customCompositionUnderlines[i].startOffset += baseOffset;
- m_customCompositionUnderlines[i].endOffset += baseOffset;
+ for (auto& underline : m_customCompositionUnderlines) {
+ underline.startOffset += baseOffset;
+ underline.endOffset += baseOffset;
}
if (baseNode->renderer())
baseNode->renderer()->repaint();
@@ -1907,8 +1868,11 @@ void Editor::learnSpelling()
}
#if !PLATFORM(IOS)
+
void Editor::advanceToNextMisspelling(bool startBeforeSelection)
{
+ Ref<Frame> protection(m_frame);
+
// The basic approach is to search in two phases - from the selection end to the end of the doc, and
// then we wrap and search from the doc start to (approximately) where we started.
@@ -1937,19 +1901,21 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
// when spell checking the whole document before sending the message.
// In that case the document might not be editable, but there are editable pockets that need to be spell checked.
- position = firstEditablePositionAfterPositionInRoot(position, document().documentElement()).deepEquivalent();
+ position = VisiblePosition(firstEditablePositionAfterPositionInRoot(position, document().documentElement())).deepEquivalent();
if (position.isNull())
return;
Position rangeCompliantPosition = position.parentAnchoredEquivalent();
- spellingSearchRange->setStart(rangeCompliantPosition.deprecatedNode(), rangeCompliantPosition.deprecatedEditingOffset(), IGNORE_EXCEPTION);
+ if (rangeCompliantPosition.deprecatedNode())
+ spellingSearchRange->setStart(*rangeCompliantPosition.deprecatedNode(), rangeCompliantPosition.deprecatedEditingOffset());
startedWithSelection = false; // won't need to wrap
}
// topNode defines the whole range we want to operate on
- Node* topNode = highestEditableRoot(position);
+ auto* topNode = highestEditableRoot(position);
// FIXME: lastOffsetForEditing() is wrong here if editingIgnoresContent(highestEditableRoot()) returns true (e.g. a <table>)
- spellingSearchRange->setEnd(topNode, lastOffsetForEditing(topNode), IGNORE_EXCEPTION);
+ if (topNode)
+ spellingSearchRange->setEnd(*topNode, lastOffsetForEditing(*topNode));
// If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking
// at a word boundary. Going back by one char and then forward by a word does the trick.
@@ -1960,7 +1926,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
// else we were already at the start of the editable node
}
- if (spellingSearchRange->collapsed(IGNORE_EXCEPTION))
+ if (spellingSearchRange->collapsed())
return; // nothing to search in
// Get the spell checker if it is available
@@ -1970,7 +1936,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
// We go to the end of our first range instead of the start of it, just to be sure
// we don't get foiled by any word boundary problems at the start. It means we might
// do a tiny bit more searching.
- Node* searchEndNodeAfterWrap = spellingSearchRange->endContainer();
+ Node& searchEndNodeAfterWrap = spellingSearchRange->endContainer();
int searchEndOffsetAfterWrap = spellingSearchRange->endOffset();
int misspellingOffset = 0;
@@ -1985,7 +1951,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
String foundItem;
RefPtr<Range> firstMisspellingRange;
if (unifiedTextCheckerEnabled()) {
- grammarSearchRange = spellingSearchRange->cloneRange(IGNORE_EXCEPTION);
+ grammarSearchRange = spellingSearchRange->cloneRange();
foundItem = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
if (isSpelling) {
misspelledWord = foundItem;
@@ -1998,12 +1964,12 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
misspelledWord = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange);
#if USE(GRAMMAR_CHECKING)
- grammarSearchRange = spellingSearchRange->cloneRange(IGNORE_EXCEPTION);
+ grammarSearchRange = spellingSearchRange->cloneRange();
if (!misspelledWord.isEmpty()) {
// Stop looking at start of next misspelled word
- CharacterIterator chars(grammarSearchRange.get());
+ CharacterIterator chars(*grammarSearchRange);
chars.advance(misspellingOffset);
- grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION);
+ grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset());
}
if (isGrammarCheckingEnabled())
@@ -2014,12 +1980,13 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
// If we found neither bad grammar nor a misspelled word, wrap and try again (but don't bother if we started at the beginning of the
// block rather than at a selection).
if (startedWithSelection && !misspelledWord && !badGrammarPhrase) {
- spellingSearchRange->setStart(topNode, 0, IGNORE_EXCEPTION);
+ if (topNode)
+ spellingSearchRange->setStart(*topNode, 0);
// going until the end of the very first chunk we tested is far enough
- spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap, IGNORE_EXCEPTION);
+ spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap);
if (unifiedTextCheckerEnabled()) {
- grammarSearchRange = spellingSearchRange->cloneRange(IGNORE_EXCEPTION);
+ grammarSearchRange = spellingSearchRange->cloneRange();
foundItem = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail);
if (isSpelling) {
misspelledWord = foundItem;
@@ -2032,12 +1999,12 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
misspelledWord = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange);
#if USE(GRAMMAR_CHECKING)
- grammarSearchRange = spellingSearchRange->cloneRange(IGNORE_EXCEPTION);
+ grammarSearchRange = spellingSearchRange->cloneRange();
if (!misspelledWord.isEmpty()) {
// Stop looking at start of next misspelled word
- CharacterIterator chars(grammarSearchRange.get());
+ CharacterIterator chars(*grammarSearchRange);
chars.advance(misspellingOffset);
- grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION);
+ grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset());
}
if (isGrammarCheckingEnabled())
@@ -2060,7 +2027,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
// FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph
RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + grammarDetail.location, grammarDetail.length);
- m_frame.selection().setSelection(VisibleSelection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY));
+ m_frame.selection().setSelection(VisibleSelection(*badGrammarRange, SEL_DEFAULT_AFFINITY));
m_frame.selection().revealSelection();
client()->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
@@ -2072,13 +2039,14 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
// a marker so we draw the red squiggle later.
RefPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, misspelledWord.length());
- m_frame.selection().setSelection(VisibleSelection(misspellingRange.get(), DOWNSTREAM));
+ m_frame.selection().setSelection(VisibleSelection(*misspellingRange, DOWNSTREAM));
m_frame.selection().revealSelection();
client()->updateSpellingUIWithMisspelledWord(misspelledWord);
document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling);
}
}
+
#endif // !PLATFORM(IOS)
String Editor::misspelledWordAtCaretOrRange(Node* clickedNode) const
@@ -2106,7 +2074,7 @@ String Editor::misspelledWordAtCaretOrRange(Node* clickedNode) const
int wordLength = word.length();
int misspellingLocation = -1;
int misspellingLength = 0;
- textChecker()->checkSpellingOfString(word.deprecatedCharacters(), wordLength, &misspellingLocation, &misspellingLength);
+ textChecker()->checkSpellingOfString(word, &misspellingLocation, &misspellingLength);
return misspellingLength == wordLength ? word : String();
}
@@ -2120,7 +2088,7 @@ String Editor::misspelledSelectionString() const
int misspellingLocation = -1;
int misspellingLength = 0;
- textChecker()->checkSpellingOfString(selectedString.deprecatedCharacters(), length, &misspellingLocation, &misspellingLength);
+ textChecker()->checkSpellingOfString(selectedString, &misspellingLocation, &misspellingLength);
// The selection only counts as misspelled if the selected text is exactly one misspelled word
if (misspellingLength != length)
@@ -2153,7 +2121,7 @@ Vector<String> Editor::guessesForMisspelledWord(const String& word) const
Vector<String> guesses;
if (client())
- textChecker()->getGuessesForWord(word, String(), guesses);
+ textChecker()->getGuessesForWord(word, String(), m_frame.selection().selection(), guesses);
return guesses;
}
@@ -2161,13 +2129,13 @@ Vector<String> Editor::guessesForMisspelledOrUngrammatical(bool& misspelled, boo
{
if (unifiedTextCheckerEnabled()) {
RefPtr<Range> range;
- FrameSelection& frameSelection = m_frame.selection();
- if (frameSelection.isCaret() && behavior().shouldAllowSpellingSuggestionsWithoutSelection()) {
- VisibleSelection wordSelection = VisibleSelection(frameSelection.base());
+ VisibleSelection selection = m_frame.selection().selection();
+ if (selection.isCaret() && behavior().shouldAllowSpellingSuggestionsWithoutSelection()) {
+ VisibleSelection wordSelection = VisibleSelection(selection.base());
wordSelection.expandUsingGranularity(WordGranularity);
range = wordSelection.toNormalizedRange();
} else
- range = frameSelection.toNormalizedRange();
+ range = selection.toNormalizedRange();
if (!range)
return Vector<String>();
return TextCheckingHelper(client(), range).guessesForMisspelledOrUngrammaticalRange(isGrammarCheckingEnabled(), misspelled, ungrammatical);
@@ -2224,6 +2192,8 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelecti
void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping, bool doReplacement)
{
+ Ref<Frame> protection(m_frame);
+
#if PLATFORM(IOS)
UNUSED_PARAM(selectionAfterTyping);
UNUSED_PARAM(doReplacement);
@@ -2289,19 +2259,19 @@ void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart,
// If autocorrected word is non empty, replace the misspelled word by this word.
if (!autocorrectedString.isEmpty()) {
- VisibleSelection newSelection(misspellingRange.get(), DOWNSTREAM);
+ VisibleSelection newSelection(*misspellingRange, DOWNSTREAM);
if (newSelection != m_frame.selection().selection()) {
if (!m_frame.selection().shouldChangeSelection(newSelection))
return;
m_frame.selection().setSelection(newSelection);
}
- if (!m_frame.editor().shouldInsertText(autocorrectedString, misspellingRange.get(), EditorInsertActionTyped))
+ if (!m_frame.editor().shouldInsertText(autocorrectedString, misspellingRange.get(), EditorInsertAction::Typed))
return;
- m_frame.editor().replaceSelectionWithText(autocorrectedString, false, false);
+ m_frame.editor().replaceSelectionWithText(autocorrectedString, false, false, EditActionInsert);
// Reset the charet one character further.
- m_frame.selection().moveTo(m_frame.selection().end());
+ m_frame.selection().moveTo(m_frame.selection().selection().end());
m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity);
}
@@ -2329,11 +2299,11 @@ void Editor::markMisspellingsOrBadGrammar(const VisibleSelection& selection, boo
return;
// If we're not in an editable node, bail.
- Node* editableNode = searchRange->startContainer();
- if (!editableNode || !editableNode->hasEditableStyle())
+ Node& editableNode = searchRange->startContainer();
+ if (!editableNode.hasEditableStyle())
return;
- if (!isSpellCheckingEnabledFor(editableNode))
+ if (!isSpellCheckingEnabledFor(&editableNode))
return;
// Get the spell checker if it is available
@@ -2362,15 +2332,19 @@ bool Editor::isSpellCheckingEnabledFor(Node* node) const
{
if (!node)
return false;
- const Element* focusedElement = node->isElementNode() ? toElement(node) : node->parentElement();
- if (!focusedElement)
+ Element* element = is<Element>(*node) ? downcast<Element>(node) : node->parentElement();
+ if (!element)
return false;
- return focusedElement->isSpellCheckingEnabled();
+ if (element->isInUserAgentShadowTree()) {
+ if (HTMLTextFormControlElement* textControl = enclosingTextFormControl(firstPositionInOrBeforeNode(element)))
+ return textControl->isSpellCheckingEnabled();
+ }
+ return element->isSpellCheckingEnabled();
}
bool Editor::isSpellCheckingEnabledInFocusedNode() const
{
- return isSpellCheckingEnabledFor(m_frame.selection().start().deprecatedNode());
+ return isSpellCheckingEnabledFor(m_frame.selection().selection().start().deprecatedNode());
}
void Editor::markMisspellings(const VisibleSelection& selection, RefPtr<Range>& firstMisspellingRange)
@@ -2403,33 +2377,33 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textC
return;
// If we're not in an editable node, bail.
- Node* editableNode = spellingRange->startContainer();
- if (!editableNode || !editableNode->hasEditableStyle())
+ Node& editableNode = spellingRange->startContainer();
+ if (!editableNode.hasEditableStyle())
return;
- if (!isSpellCheckingEnabledFor(editableNode))
+ if (!isSpellCheckingEnabledFor(&editableNode))
return;
Range* rangeToCheck = shouldMarkGrammar ? grammarRange : spellingRange;
TextCheckingParagraph paragraphToCheck(rangeToCheck);
- if (paragraphToCheck.isRangeEmpty() || paragraphToCheck.isEmpty())
+ if (paragraphToCheck.isEmpty())
return;
RefPtr<Range> paragraphRange = paragraphToCheck.paragraphRange();
bool asynchronous = m_frame.settings().asynchronousSpellCheckingEnabled() && !shouldShowCorrectionPanel;
// In asynchronous mode, we intentionally check paragraph-wide sentence.
- RefPtr<SpellCheckRequest> request = SpellCheckRequest::create(resolveTextCheckingTypeMask(textCheckingOptions), TextCheckingProcessIncremental, asynchronous ? paragraphRange : rangeToCheck, paragraphRange);
+ const auto resolvedOptions = resolveTextCheckingTypeMask(editableNode, textCheckingOptions);
+ auto request = SpellCheckRequest::create(resolvedOptions, TextCheckingProcessIncremental, asynchronous ? paragraphRange : rangeToCheck, paragraphRange);
if (asynchronous) {
- m_spellChecker->requestCheckingFor(request);
+ m_spellChecker->requestCheckingFor(WTFMove(request));
return;
}
Vector<TextCheckingResult> results;
- checkTextOfParagraph(textChecker(), paragraphToCheck.textDeprecatedCharacters(), paragraphToCheck.textLength(),
- resolveTextCheckingTypeMask(textCheckingOptions), results);
- markAndReplaceFor(request, results);
+ checkTextOfParagraph(*textChecker(), paragraphToCheck.text(), resolvedOptions, results, m_frame.selection().selection());
+ markAndReplaceFor(WTFMove(request), results);
}
static bool isAutomaticTextReplacementType(TextCheckingType type)
@@ -2453,24 +2427,25 @@ static bool isAutomaticTextReplacementType(TextCheckingType type)
static void correctSpellcheckingPreservingTextCheckingParagraph(TextCheckingParagraph& paragraph, PassRefPtr<Range> rangeToReplace, const String& replacement, int resultLocation, int resultLength)
{
- ContainerNode* scope = toContainerNode(highestAncestor(paragraph.paragraphRange()->startContainer()));
+ auto& scope = downcast<ContainerNode>(paragraph.paragraphRange()->startContainer().rootNode());
size_t paragraphLocation;
size_t paragraphLength;
- TextIterator::getLocationAndLengthFromRange(scope, paragraph.paragraphRange().get(), paragraphLocation, paragraphLength);
+ TextIterator::getLocationAndLengthFromRange(&scope, paragraph.paragraphRange().get(), paragraphLocation, paragraphLength);
applyCommand(SpellingCorrectionCommand::create(rangeToReplace, replacement));
// TextCheckingParagraph may be orphaned after SpellingCorrectionCommand mutated DOM.
// See <rdar://10305315>, http://webkit.org/b/89526.
- RefPtr<Range> newParagraphRange = TextIterator::rangeFromLocationAndLength(scope, paragraphLocation, paragraphLength + replacement.length() - resultLength);
+ RefPtr<Range> newParagraphRange = TextIterator::rangeFromLocationAndLength(&scope, paragraphLocation, paragraphLength + replacement.length() - resultLength);
paragraph = TextCheckingParagraph(TextIterator::subrange(newParagraphRange.get(), resultLocation, replacement.length()), newParagraphRange);
}
void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vector<TextCheckingResult>& results)
{
+ Ref<Frame> protection(m_frame);
ASSERT(request);
TextCheckingTypeMask textCheckingOptions = request->data().mask();
@@ -2479,7 +2454,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect
const bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling;
const bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
const bool shouldMarkLink = textCheckingOptions & TextCheckingTypeLink;
- const bool shouldPerformReplacement = textCheckingOptions & TextCheckingTypeReplacement;
+ const bool shouldPerformReplacement = textCheckingOptions & (TextCheckingTypeQuote | TextCheckingTypeDash | TextCheckingTypeReplacement);
const bool shouldShowCorrectionPanel = textCheckingOptions & TextCheckingTypeShowCorrectionPanel;
const bool shouldCheckForCorrection = shouldShowCorrectionPanel || (textCheckingOptions & TextCheckingTypeCorrection);
#if !USE(AUTOCORRECTION_PANEL)
@@ -2494,10 +2469,10 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect
bool adjustSelectionForParagraphBoundaries = false;
if (shouldPerformReplacement || shouldMarkSpelling || shouldCheckForCorrection) {
- if (m_frame.selection().selectionType() == VisibleSelection::CaretSelection) {
+ if (m_frame.selection().selection().selectionType() == VisibleSelection::CaretSelection) {
// Attempt to save the caret position so we can restore it later if needed
- Position caretPosition = m_frame.selection().end();
- selectionOffset = paragraph.offsetTo(caretPosition, ASSERT_NO_EXCEPTION);
+ Position caretPosition = m_frame.selection().selection().end();
+ selectionOffset = paragraph.offsetTo(caretPosition).releaseReturnValue();
restoreSelectionAfterChange = true;
if (selectionOffset > 0 && (selectionOffset > paragraph.textLength() || paragraph.textCharAt(selectionOffset - 1) == newlineCharacter))
adjustSelectionForParagraphBoundaries = true;
@@ -2515,7 +2490,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect
const int resultLength = results[i].length;
const int resultEndLocation = resultLocation + resultLength;
const String& replacement = results[i].replacement;
- const bool resultEndsAtAmbiguousBoundary = useAmbiguousBoundaryOffset && resultEndLocation == selectionOffset - 1;
+ const bool resultEndsAtAmbiguousBoundary = useAmbiguousBoundaryOffset && selectionOffset - 1 <= resultEndLocation;
// Only mark misspelling if:
// 1. Current text checking isn't done for autocorrection, in which case shouldMarkSpelling is false.
@@ -2528,16 +2503,14 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect
RefPtr<Range> misspellingRange = paragraph.subrange(resultLocation, resultLength);
if (!m_alternativeTextController->isSpellingMarkerAllowed(misspellingRange))
continue;
- misspellingRange->startContainer()->document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling, replacement);
+ misspellingRange->startContainer().document().markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling, replacement);
} else if (shouldMarkGrammar && resultType == TextCheckingTypeGrammar && paragraph.checkingRangeCovers(resultLocation, resultLength)) {
ASSERT(resultLength > 0 && resultLocation >= 0);
- const Vector<GrammarDetail>& details = results[i].details;
- for (unsigned j = 0; j < details.size(); j++) {
- const GrammarDetail& detail = details[j];
+ for (auto& detail : results[i].details) {
ASSERT(detail.length > 0 && detail.location >= 0);
if (paragraph.checkingRangeCovers(resultLocation + detail.location, detail.length)) {
RefPtr<Range> badGrammarRange = paragraph.subrange(resultLocation + detail.location, detail.length);
- badGrammarRange->startContainer()->document().markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail.userDescription);
+ badGrammarRange->startContainer().document().markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail.userDescription);
}
}
} else if (resultEndLocation <= spellingRangeEndOffset && resultEndLocation >= paragraph.checkingStart()
@@ -2578,7 +2551,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect
continue;
}
- VisibleSelection selectionToReplace(rangeToReplace.get(), DOWNSTREAM);
+ VisibleSelection selectionToReplace(*rangeToReplace, DOWNSTREAM);
if (selectionToReplace != m_frame.selection().selection()) {
if (!m_frame.selection().shouldChangeSelection(selectionToReplace))
continue;
@@ -2590,7 +2563,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect
restoreSelectionAfterChange = false;
if (canEditRichly())
applyCommand(CreateLinkCommand::create(document(), replacement));
- } else if (canEdit() && shouldInsertText(replacement, rangeToReplace.get(), EditorInsertActionTyped)) {
+ } else if (canEdit() && shouldInsertText(replacement, rangeToReplace.get(), EditorInsertAction::Typed)) {
correctSpellcheckingPreservingTextCheckingParagraph(paragraph, rangeToReplace, replacement, resultLocation, resultLength);
if (AXObjectCache* cache = document().existingAXObjectCache()) {
@@ -2607,9 +2580,13 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect
if (resultLocation < selectionOffset)
selectionOffset += replacement.length() - resultLength;
- // Add a marker so that corrections can easily be undone and won't be re-corrected.
- if (resultType == TextCheckingTypeCorrection)
- m_alternativeTextController->markCorrection(paragraph.subrange(resultLocation, replacement.length()), replacedString);
+ if (resultType == TextCheckingTypeCorrection) {
+ RefPtr<Range> replacementRange = paragraph.subrange(resultLocation, replacement.length());
+ m_alternativeTextController->recordAutocorrectionResponse(AutocorrectionResponse::Accepted, replacedString, replacementRange);
+
+ // Add a marker so that corrections can easily be undone and won't be re-corrected.
+ m_alternativeTextController->markCorrection(WTFMove(replacementRange), replacedString);
+ }
}
}
}
@@ -2625,7 +2602,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect
m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity);
} else {
// If this fails for any reason, the fallback is to go one position beyond the last replacement
- m_frame.selection().moveTo(m_frame.selection().end());
+ m_frame.selection().moveTo(m_frame.selection().selection().end());
m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity);
}
}
@@ -2640,14 +2617,14 @@ void Editor::changeBackToReplacedString(const String& replacedString)
return;
RefPtr<Range> selection = selectedRange();
- if (!shouldInsertText(replacedString, selection.get(), EditorInsertActionPasted))
+ if (!shouldInsertText(replacedString, selection.get(), EditorInsertAction::Pasted))
return;
- m_alternativeTextController->recordAutocorrectionResponseReversed(replacedString, selection);
+ m_alternativeTextController->recordAutocorrectionResponse(AutocorrectionResponse::Reverted, replacedString, selection);
TextCheckingParagraph paragraph(selection);
- replaceSelectionWithText(replacedString, false, false);
+ replaceSelectionWithText(replacedString, false, false, EditActionInsert);
RefPtr<Range> changedRange = paragraph.subrange(paragraph.checkingStart(), replacedString.length());
- changedRange->startContainer()->document().markers().addMarker(changedRange.get(), DocumentMarker::Replacement, String());
+ changedRange->startContainer().document().markers().addMarker(changedRange.get(), DocumentMarker::Replacement, String());
m_alternativeTextController->markReversed(changedRange.get());
#else
ASSERT_NOT_REACHED();
@@ -2749,9 +2726,9 @@ void Editor::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionA
// of marker that contains the word in question, and remove marker on that whole range.
RefPtr<Range> wordRange = Range::create(document(), startOfFirstWord.deepEquivalent(), endOfLastWord.deepEquivalent());
- Vector<DocumentMarker*> markers = document().markers().markersInRange(wordRange.get(), DocumentMarker::DictationAlternatives);
- for (size_t i = 0; i < markers.size(); ++i)
- m_alternativeTextController->removeDictationAlternativesForMarker(markers[i]);
+ Vector<RenderedDocumentMarker*> markers = document().markers().markersInRange(wordRange.get(), DocumentMarker::DictationAlternatives);
+ for (auto* marker : markers)
+ m_alternativeTextController->removeDictationAlternativesForMarker(*marker);
#if PLATFORM(IOS)
document().markers().removeMarkers(wordRange.get(), DocumentMarker::Spelling | DocumentMarker::CorrectionIndicator | DocumentMarker::SpellCheckingExemption | DocumentMarker::DictationAlternatives | DocumentMarker::DictationPhraseWithAlternatives, DocumentMarkerController::RemovePartiallyOverlappingMarker);
@@ -2780,7 +2757,7 @@ PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint)
IntPoint framePoint = frameView->windowToContents(windowPoint);
VisibleSelection selection(frame->visiblePositionForPoint(framePoint));
- return avoidIntersectionWithDeleteButtonController(selection.toNormalizedRange().get());
+ return selection.toNormalizedRange();
}
void Editor::revealSelectionAfterEditingOperation(const ScrollAlignment& alignment, RevealExtentOption revealExtentOption)
@@ -2788,33 +2765,39 @@ void Editor::revealSelectionAfterEditingOperation(const ScrollAlignment& alignme
if (m_ignoreCompositionSelectionChange)
return;
- m_frame.selection().revealSelection(alignment, revealExtentOption);
+#if PLATFORM(IOS)
+ SelectionRevealMode revealMode = SelectionRevealMode::RevealUpToMainFrame;
+#else
+ SelectionRevealMode revealMode = SelectionRevealMode::Reveal;
+#endif
+
+ m_frame.selection().revealSelection(revealMode, alignment, revealExtentOption);
}
-void Editor::setIgnoreCompositionSelectionChange(bool ignore)
+void Editor::setIgnoreCompositionSelectionChange(bool ignore, RevealSelection shouldRevealExistingSelection)
{
if (m_ignoreCompositionSelectionChange == ignore)
return;
m_ignoreCompositionSelectionChange = ignore;
#if PLATFORM(IOS)
- // FIXME: Merge this to open source https://bugs.webkit.org/show_bug.cgi?id=38830
+ // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830>
if (!ignore)
- notifyComponentsOnChangedSelection(m_frame.selection().selection(), 0);
+ respondToChangedSelection(m_frame.selection().selection(), 0);
#endif
- if (!ignore)
+ if (!ignore && shouldRevealExistingSelection == RevealSelection::Yes)
revealSelectionAfterEditingOperation(ScrollAlignment::alignToEdgeIfNeeded, RevealExtent);
}
-PassRefPtr<Range> Editor::compositionRange() const
+RefPtr<Range> Editor::compositionRange() const
{
if (!m_compositionNode)
- return 0;
+ return nullptr;
unsigned length = m_compositionNode->length();
unsigned start = std::min(m_compositionStart, length);
unsigned end = std::min(std::max(start, m_compositionEnd), length);
if (start >= end)
- return 0;
+ return nullptr;
return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end);
}
@@ -2822,10 +2805,11 @@ bool Editor::getCompositionSelection(unsigned& selectionStart, unsigned& selecti
{
if (!m_compositionNode)
return false;
- Position start = m_frame.selection().start();
+ const VisibleSelection& selection = m_frame.selection().selection();
+ Position start = selection.start();
if (start.deprecatedNode() != m_compositionNode)
return false;
- Position end = m_frame.selection().end();
+ Position end = selection.end();
if (end.deprecatedNode() != m_compositionNode)
return false;
@@ -2860,7 +2844,7 @@ void Editor::transpose()
RefPtr<Range> range = makeRange(previous, next);
if (!range)
return;
- VisibleSelection newSelection(range.get(), DOWNSTREAM);
+ VisibleSelection newSelection(*range, DOWNSTREAM);
// Transpose the two characters.
String text = plainText(range.get());
@@ -2876,22 +2860,34 @@ void Editor::transpose()
}
// Insert the transposed characters.
- if (!shouldInsertText(transposed, range.get(), EditorInsertActionTyped))
+ if (!shouldInsertText(transposed, range.get(), EditorInsertAction::Typed))
return;
- replaceSelectionWithText(transposed, false, false);
+ replaceSelectionWithText(transposed, false, false, EditActionInsert);
+}
+
+void Editor::addRangeToKillRing(const Range& range, KillRingInsertionMode mode)
+{
+ addTextToKillRing(plainText(&range), mode);
}
-void Editor::addToKillRing(Range* range, bool prepend)
+void Editor::addTextToKillRing(const String& text, KillRingInsertionMode mode)
{
if (m_shouldStartNewKillRingSequence)
killRing().startNewSequence();
- String text = plainText(range);
- if (prepend)
+ m_shouldStartNewKillRingSequence = false;
+
+ // If the kill was from a backwards motion, prepend to the kill ring.
+ // This will ensure that alternating forward and backward kills will
+ // build up the original string in the kill ring without permuting it.
+ switch (mode) {
+ case KillRingInsertionMode::PrependText:
killRing().prepend(text);
- else
+ break;
+ case KillRingInsertionMode::AppendText:
killRing().append(text);
- m_shouldStartNewKillRingSequence = false;
+ break;
+ }
}
void Editor::startAlternativeTextUITimer()
@@ -2910,8 +2906,10 @@ void Editor::dismissCorrectionPanelAsIgnored()
m_alternativeTextController->dismiss(ReasonForDismissingAlternativeTextIgnored);
}
-void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, FrameSelection::SetSelectionOptions options)
+void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, FrameSelection::SetSelectionOptions options)
{
+ Ref<Frame> protection(m_frame);
+
// If the new selection is orphaned, then don't update the selection.
if (newSelection.start().isOrphan() || newSelection.end().isOrphan())
return;
@@ -2932,7 +2930,7 @@ void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection,
// does not call EditorClient::respondToChangedSelection(), which, on the Mac, sends selection change notifications and
// starts a new kill ring sequence, but we want to do these things (matches AppKit).
#if PLATFORM(IOS)
- // FIXME: Merge this to open source https://bugs.webkit.org/show_bug.cgi?id=38830
+ // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830>
if (m_ignoreCompositionSelectionChange)
return;
#endif
@@ -2945,11 +2943,9 @@ String Editor::selectedText() const
return selectedText(TextIteratorDefaultBehavior);
}
-String Editor::selectedTextForClipboard() const
+String Editor::selectedTextForDataTransfer() const
{
- if (m_frame.settings().selectionIncludesAltImageText())
- return selectedText(TextIteratorEmitsImageAltText);
- return selectedText();
+ return selectedText(TextIteratorEmitsImageAltText);
}
String Editor::selectedText(TextIteratorBehavior behavior) const
@@ -2969,12 +2965,9 @@ static inline void collapseCaretWidth(IntRect& rect)
IntRect Editor::firstRectForRange(Range* range) const
{
- ASSERT(range->startContainer());
- ASSERT(range->endContainer());
-
VisiblePosition startVisiblePosition(range->startPosition(), DOWNSTREAM);
- if (range->collapsed(ASSERT_NO_EXCEPTION)) {
+ if (range->collapsed()) {
// FIXME: Getting caret rect and removing caret width is a very roundabout way to get collapsed range location.
// In particular, width adjustment doesn't work for rotated text.
IntRect startCaretRect = RenderedPosition(startVisiblePosition).absoluteRect();
@@ -3014,22 +3007,20 @@ bool Editor::shouldChangeSelection(const VisibleSelection& oldSelection, const V
return client() && client()->shouldChangeSelectedRange(oldSelection.toNormalizedRange().get(), newSelection.toNormalizedRange().get(), affinity, stillSelecting);
}
-void Editor::computeAndSetTypingStyle(StyleProperties* style, EditAction editingAction)
+void Editor::computeAndSetTypingStyle(EditingStyle& style, EditAction editingAction)
{
- if (!style || style->isEmpty()) {
+ if (style.isEmpty()) {
m_frame.selection().clearTypingStyle();
return;
}
// Calculate the current typing style.
RefPtr<EditingStyle> typingStyle;
- if (m_frame.selection().typingStyle()) {
- typingStyle = m_frame.selection().typingStyle()->copy();
- typingStyle->overrideWithStyle(style);
- } else
- typingStyle = EditingStyle::create(style);
-
- typingStyle->prepareToApplyAt(m_frame.selection().selection().visibleStart().deepEquivalent(), EditingStyle::PreserveWritingDirection);
+ if (auto existingTypingStyle = m_frame.selection().typingStyle())
+ typingStyle = existingTypingStyle->copy();
+ else
+ typingStyle = EditingStyle::create();
+ typingStyle->overrideTypingStyleAt(style, m_frame.selection().selection().visibleStart().deepEquivalent());
// Handle block styles, substracting these from the typing style.
RefPtr<EditingStyle> blockStyle = typingStyle->extractAndRemoveBlockProperties();
@@ -3040,6 +3031,11 @@ void Editor::computeAndSetTypingStyle(StyleProperties* style, EditAction editing
m_frame.selection().setTypingStyle(typingStyle);
}
+void Editor::computeAndSetTypingStyle(StyleProperties& properties, EditAction editingAction)
+{
+ return computeAndSetTypingStyle(EditingStyle::create(&properties), editingAction);
+}
+
void Editor::textFieldDidBeginEditing(Element* e)
{
if (client())
@@ -3081,29 +3077,29 @@ void Editor::textDidChangeInTextArea(Element* e)
void Editor::applyEditingStyleToBodyElement() const
{
- RefPtr<NodeList> list = document().getElementsByTagName("body");
- unsigned len = list->length();
- for (unsigned i = 0; i < len; i++)
- applyEditingStyleToElement(toElement(list->item(i)));
+ auto collection = document().getElementsByTagName(HTMLNames::bodyTag.localName());
+ unsigned length = collection->length();
+ for (unsigned i = 0; i < length; ++i)
+ applyEditingStyleToElement(collection->item(i));
}
void Editor::applyEditingStyleToElement(Element* element) const
{
- if (!element)
- return;
- ASSERT(element->isStyledElement());
- if (!element->isStyledElement())
+ ASSERT(!element || is<StyledElement>(*element));
+ if (!is<StyledElement>(element))
return;
// Mutate using the CSSOM wrapper so we get the same event behavior as a script.
- CSSStyleDeclaration* style = toStyledElement(element)->style();
- style->setPropertyInternal(CSSPropertyWordWrap, "break-word", false, IGNORE_EXCEPTION);
- style->setPropertyInternal(CSSPropertyWebkitNbspMode, "space", false, IGNORE_EXCEPTION);
- style->setPropertyInternal(CSSPropertyWebkitLineBreak, "after-white-space", false, IGNORE_EXCEPTION);
+ CSSStyleDeclaration* style = downcast<StyledElement>(*element).cssomStyle();
+ style->setPropertyInternal(CSSPropertyWordWrap, "break-word", false);
+ style->setPropertyInternal(CSSPropertyWebkitNbspMode, "space", false);
+ style->setPropertyInternal(CSSPropertyWebkitLineBreak, "after-white-space", false);
}
bool Editor::findString(const String& target, FindOptions options)
{
+ Ref<Frame> protection(m_frame);
+
VisibleSelection selection = m_frame.selection().selection();
RefPtr<Range> resultRange = rangeOfString(target, selection.firstRange().get(), options);
@@ -3111,27 +3107,18 @@ bool Editor::findString(const String& target, FindOptions options)
if (!resultRange)
return false;
- m_frame.selection().setSelection(VisibleSelection(resultRange.get(), DOWNSTREAM));
- m_frame.selection().revealSelection();
- return true;
-}
-
-PassRefPtr<Range> Editor::findStringAndScrollToVisible(const String& target, Range* previousMatch, FindOptions options)
-{
- RefPtr<Range> nextMatch = rangeOfString(target, previousMatch, options);
- if (!nextMatch)
- return 0;
+ m_frame.selection().setSelection(VisibleSelection(*resultRange, DOWNSTREAM));
- nextMatch->firstNode()->renderer()->scrollRectToVisible(nextMatch->boundingBox(),
- ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded);
+ if (!(options & DoNotRevealSelection))
+ m_frame.selection().revealSelection();
- return nextMatch.release();
+ return true;
}
-PassRefPtr<Range> Editor::rangeOfString(const String& target, Range* referenceRange, FindOptions options)
+RefPtr<Range> Editor::rangeOfString(const String& target, Range* referenceRange, FindOptions options)
{
if (target.isEmpty())
- return 0;
+ return nullptr;
// Start from an edge of the reference range, if there's a reference range that's not in shadow content. Which edge
// is used depends on whether we're searching forward or backward, and whether startInSelection is set.
@@ -3146,19 +3133,19 @@ PassRefPtr<Range> Editor::rangeOfString(const String& target, Range* referenceRa
searchRange->setEnd(startInReferenceRange ? referenceRange->endPosition() : referenceRange->startPosition());
}
- RefPtr<Node> shadowTreeRoot = referenceRange && referenceRange->startContainer() ? referenceRange->startContainer()->nonBoundaryShadowTreeRootNode() : 0;
+ RefPtr<ShadowRoot> shadowTreeRoot = referenceRange ? referenceRange->startContainer().containingShadowRoot() : nullptr;
if (shadowTreeRoot) {
if (forward)
- searchRange->setEnd(shadowTreeRoot.get(), shadowTreeRoot->childNodeCount());
+ searchRange->setEnd(*shadowTreeRoot, shadowTreeRoot->countChildNodes());
else
- searchRange->setStart(shadowTreeRoot.get(), 0);
+ searchRange->setStart(*shadowTreeRoot, 0);
}
- RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, options));
+ RefPtr<Range> resultRange = findPlainText(*searchRange, target, options);
// If we started in the reference range and the found range exactly matches the reference range, find again.
// Build a selection with the found range to remove collapsed whitespace.
// Compare ranges instead of selection objects to ignore the way that the current selection was made.
- if (startInReferenceRange && areRangesEqual(VisibleSelection(resultRange.get()).toNormalizedRange().get(), referenceRange)) {
+ if (startInReferenceRange && areRangesEqual(VisibleSelection(*resultRange).toNormalizedRange().get(), referenceRange)) {
searchRange = rangeOfContents(document());
if (forward)
searchRange->setStart(referenceRange->endPosition());
@@ -3167,48 +3154,49 @@ PassRefPtr<Range> Editor::rangeOfString(const String& target, Range* referenceRa
if (shadowTreeRoot) {
if (forward)
- searchRange->setEnd(shadowTreeRoot.get(), shadowTreeRoot->childNodeCount());
+ searchRange->setEnd(*shadowTreeRoot, shadowTreeRoot->countChildNodes());
else
- searchRange->setStart(shadowTreeRoot.get(), 0);
+ searchRange->setStart(*shadowTreeRoot, 0);
}
- resultRange = findPlainText(searchRange.get(), target, options);
+ resultRange = findPlainText(*searchRange, target, options);
}
// If nothing was found in the shadow tree, search in main content following the shadow tree.
- if (resultRange->collapsed(ASSERT_NO_EXCEPTION) && shadowTreeRoot) {
+ if (resultRange->collapsed() && shadowTreeRoot) {
searchRange = rangeOfContents(document());
- if (forward)
- searchRange->setStartAfter(shadowTreeRoot->shadowHost());
- else
- searchRange->setEndBefore(shadowTreeRoot->shadowHost());
+ if (shadowTreeRoot->shadowHost()) {
+ if (forward)
+ searchRange->setStartAfter(*shadowTreeRoot->shadowHost());
+ else
+ searchRange->setEndBefore(*shadowTreeRoot->shadowHost());
+ }
- resultRange = findPlainText(searchRange.get(), target, options);
+ resultRange = findPlainText(*searchRange, target, options);
}
// If we didn't find anything and we're wrapping, search again in the entire document (this will
// redundantly re-search the area already searched in some cases).
- if (resultRange->collapsed(ASSERT_NO_EXCEPTION) && options & WrapAround) {
+ if (resultRange->collapsed() && options & WrapAround) {
searchRange = rangeOfContents(document());
- resultRange = findPlainText(searchRange.get(), target, options);
+ resultRange = findPlainText(*searchRange, target, options);
// We used to return false here if we ended up with the same range that we started with
// (e.g., the reference range was already the only instance of this text). But we decided that
// this should be a success case instead, so we'll just fall through in that case.
}
- return resultRange->collapsed(ASSERT_NO_EXCEPTION) ? 0 : resultRange.release();
+ return resultRange->collapsed() ? nullptr : resultRange;
}
-static bool isFrameInRange(Frame* frame, Range* range)
+static bool isFrameInRange(Frame& frame, Range& range)
{
- bool inRange = false;
- for (HTMLFrameOwnerElement* ownerElement = frame->ownerElement(); ownerElement; ownerElement = ownerElement->document().ownerElement()) {
- if (&ownerElement->document() == &range->ownerDocument()) {
- inRange = range->intersectsNode(ownerElement, IGNORE_EXCEPTION);
- break;
+ for (auto* ownerElement = frame.ownerElement(); ownerElement; ownerElement = ownerElement->document().ownerElement()) {
+ if (&ownerElement->document() == &range.ownerDocument()) {
+ auto result = range.intersectsNode(*ownerElement);
+ return !result.hasException() && result.releaseReturnValue();
}
}
- return inRange;
+ return false;
}
unsigned Editor::countMatchesForText(const String& target, Range* range, FindOptions options, unsigned limit, bool markMatches, Vector<RefPtr<Range>>* matches)
@@ -3220,24 +3208,24 @@ unsigned Editor::countMatchesForText(const String& target, Range* range, FindOpt
if (range) {
if (&range->ownerDocument() == &document())
searchRange = range;
- else if (!isFrameInRange(&m_frame, range))
+ else if (!isFrameInRange(m_frame, *range))
return 0;
}
if (!searchRange)
searchRange = rangeOfContents(document());
- Node* originalEndContainer = searchRange->endContainer();
+ Node& originalEndContainer = searchRange->endContainer();
int originalEndOffset = searchRange->endOffset();
unsigned matchCount = 0;
do {
- RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, options & ~Backwards));
- if (resultRange->collapsed(IGNORE_EXCEPTION)) {
- if (!resultRange->startContainer()->isInShadowTree())
+ RefPtr<Range> resultRange(findPlainText(*searchRange, target, options & ~Backwards));
+ if (resultRange->collapsed()) {
+ if (!resultRange->startContainer().isInShadowTree())
break;
- searchRange->setStartAfter(resultRange->startContainer()->shadowHost(), IGNORE_EXCEPTION);
- searchRange->setEnd(originalEndContainer, originalEndOffset, IGNORE_EXCEPTION);
+ searchRange->setStartAfter(*resultRange->startContainer().shadowHost());
+ searchRange->setEnd(originalEndContainer, originalEndOffset);
continue;
}
@@ -3256,32 +3244,13 @@ unsigned Editor::countMatchesForText(const String& target, Range* range, FindOpt
// result range. There is no need to use a VisiblePosition here,
// since findPlainText will use a TextIterator to go over the visible
// text nodes.
- searchRange->setStart(resultRange->endContainer(IGNORE_EXCEPTION), resultRange->endOffset(IGNORE_EXCEPTION), IGNORE_EXCEPTION);
+ searchRange->setStart(resultRange->endContainer(), resultRange->endOffset());
Node* shadowTreeRoot = searchRange->shadowRoot();
- if (searchRange->collapsed(IGNORE_EXCEPTION) && shadowTreeRoot)
- searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount(), IGNORE_EXCEPTION);
+ if (searchRange->collapsed() && shadowTreeRoot)
+ searchRange->setEnd(*shadowTreeRoot, shadowTreeRoot->countChildNodes());
} while (true);
- if (markMatches || matches) {
- // Do a "fake" paint in order to execute the code that computes the rendered rect for each text match.
- if (m_frame.view() && m_frame.contentRenderer()) {
- document().updateLayout(); // Ensure layout is up to date.
- // FIXME: unclear if we need LegacyIOSDocumentVisibleRect.
- // FIXME: this should probably look at paintsEntireContents()
- LayoutRect visibleRect = m_frame.view()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
- if (!visibleRect.isEmpty()) {
- GraphicsContext context((PlatformGraphicsContext*)0);
- context.setPaintingDisabled(true);
-
- PaintBehavior oldBehavior = m_frame.view()->paintBehavior();
- m_frame.view()->setPaintBehavior(oldBehavior | PaintBehaviorFlattenCompositingLayers);
- m_frame.view()->paintContents(&context, enclosingIntRect(visibleRect));
- m_frame.view()->setPaintBehavior(oldBehavior);
- }
- }
- }
-
return matchCount;
}
@@ -3294,11 +3263,149 @@ void Editor::setMarkedTextMatchesAreHighlighted(bool flag)
document().markers().repaintMarkers(DocumentMarker::TextMatch);
}
-void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options)
+#if !PLATFORM(MAC)
+void Editor::selectionWillChange()
{
- m_alternativeTextController->stopPendingCorrection(oldSelection);
+}
+#endif
+
+void Editor::respondToChangedSelection(const VisibleSelection&, FrameSelection::SetSelectionOptions options)
+{
+#if PLATFORM(IOS)
+ // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830>
+ if (m_ignoreCompositionSelectionChange)
+ return;
+#endif
+
+ if (client())
+ client()->respondToChangedSelection(&m_frame);
+
+#if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS)
+ if (shouldDetectTelephoneNumbers())
+ m_telephoneNumberDetectionUpdateTimer.startOneShot(0);
+#endif
+
+ setStartNewKillRingSequence(true);
- bool closeTyping = options & FrameSelection::CloseTyping;
+ if (m_editorUIUpdateTimer.isActive())
+ return;
+
+ // Don't check spelling and grammar if the change of selection is triggered by spelling correction itself.
+ m_editorUIUpdateTimerShouldCheckSpellingAndGrammar = options & FrameSelection::CloseTyping
+ && !(options & FrameSelection::SpellCorrectionTriggered);
+ m_editorUIUpdateTimerWasTriggeredByDictation = options & FrameSelection::DictationTriggered;
+ m_editorUIUpdateTimer.startOneShot(0);
+}
+
+#if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS)
+
+bool Editor::shouldDetectTelephoneNumbers()
+{
+ if (!m_frame.document())
+ return false;
+ return document().isTelephoneNumberParsingEnabled() && TelephoneNumberDetector::isSupported();
+}
+
+void Editor::scanSelectionForTelephoneNumbers()
+{
+ if (!shouldDetectTelephoneNumbers() || !client())
+ return;
+
+ m_detectedTelephoneNumberRanges.clear();
+
+ Vector<RefPtr<Range>> markedRanges;
+
+ FrameSelection& frameSelection = m_frame.selection();
+ if (!frameSelection.isRange()) {
+ m_frame.mainFrame().servicesOverlayController().selectedTelephoneNumberRangesChanged();
+ return;
+ }
+ RefPtr<Range> selectedRange = frameSelection.toNormalizedRange();
+
+ // Extend the range a few characters in each direction to detect incompletely selected phone numbers.
+ static const int charactersToExtend = 15;
+ const VisibleSelection& visibleSelection = frameSelection.selection();
+ Position start = visibleSelection.start();
+ Position end = visibleSelection.end();
+ for (int i = 0; i < charactersToExtend; ++i) {
+ start = start.previous(Character);
+ end = end.next(Character);
+ }
+
+ FrameSelection extendedSelection;
+ extendedSelection.setStart(start);
+ extendedSelection.setEnd(end);
+ RefPtr<Range> extendedRange = extendedSelection.toNormalizedRange();
+
+ if (!extendedRange) {
+ m_frame.mainFrame().servicesOverlayController().selectedTelephoneNumberRangesChanged();
+ return;
+ }
+
+ scanRangeForTelephoneNumbers(*extendedRange, extendedRange->text(), markedRanges);
+
+ // Only consider ranges with a detected telephone number if they overlap with the actual selection range.
+ for (auto& range : markedRanges) {
+ if (rangesOverlap(range.get(), selectedRange.get()))
+ m_detectedTelephoneNumberRanges.append(range);
+ }
+
+ m_frame.mainFrame().servicesOverlayController().selectedTelephoneNumberRangesChanged();
+}
+
+void Editor::scanRangeForTelephoneNumbers(Range& range, const StringView& stringView, Vector<RefPtr<Range>>& markedRanges)
+{
+ // Don't scan for phone numbers inside editable regions.
+ Node& startNode = range.startContainer();
+ if (startNode.hasEditableStyle())
+ return;
+
+ // relativeStartPosition and relativeEndPosition are the endpoints of the phone number range,
+ // relative to the scannerPosition
+ unsigned length = stringView.length();
+ unsigned scannerPosition = 0;
+ int relativeStartPosition = 0;
+ int relativeEndPosition = 0;
+
+ auto characters = stringView.upconvertedCharacters();
+
+ while (scannerPosition < length && TelephoneNumberDetector::find(&characters[scannerPosition], length - scannerPosition, &relativeStartPosition, &relativeEndPosition)) {
+ // The convention in the Data Detectors framework is that the end position is the first character NOT in the phone number
+ // (that is, the length of the range is relativeEndPosition - relativeStartPosition). So subtract 1 to get the same
+ // convention as the old WebCore phone number parser (so that the rest of the code is still valid if we want to go back
+ // to the old parser).
+ --relativeEndPosition;
+
+ ASSERT(scannerPosition + relativeEndPosition < length);
+
+ unsigned subrangeOffset = scannerPosition + relativeStartPosition;
+ unsigned subrangeLength = relativeEndPosition - relativeStartPosition + 1;
+
+ RefPtr<Range> subrange = TextIterator::subrange(&range, subrangeOffset, subrangeLength);
+
+ markedRanges.append(subrange);
+ range.ownerDocument().markers().addMarker(subrange.get(), DocumentMarker::TelephoneNumber);
+
+ scannerPosition += relativeEndPosition + 1;
+ }
+}
+
+#endif // ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS)
+
+void Editor::updateEditorUINowIfScheduled()
+{
+ if (!m_editorUIUpdateTimer.isActive())
+ return;
+ m_editorUIUpdateTimer.stop();
+ editorUIUpdateTimerFired();
+}
+
+void Editor::editorUIUpdateTimerFired()
+{
+ VisibleSelection oldSelection = m_oldSelectionForEditorUIUpdate;
+
+ m_alternativeTextController->stopPendingCorrection(oldSelection);
+
bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabled();
bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && isGrammarCheckingEnabled();
if (isContinuousSpellCheckingEnabled) {
@@ -3325,13 +3432,10 @@ void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, Fra
newSelectedSentence = VisibleSelection(startOfSentence(newStart), endOfSentence(newStart));
}
- // Don't check spelling and grammar if the change of selection is triggered by spelling correction itself.
- bool shouldCheckSpellingAndGrammar = !(options & FrameSelection::SpellCorrectionTriggered);
-
// When typing we check spelling elsewhere, so don't redo it here.
// If this is a change in selection resulting from a delete operation,
// oldSelection may no longer be in the document.
- if (shouldCheckSpellingAndGrammar && closeTyping && oldSelection.isContentEditable() && oldSelection.start().deprecatedNode() && oldSelection.start().anchorNode()->inDocument()) {
+ if (m_editorUIUpdateTimerShouldCheckSpellingAndGrammar && oldSelection.isContentEditable() && oldSelection.start().deprecatedNode() && oldSelection.start().anchorNode()->isConnected()) {
VisiblePosition oldStart(oldSelection.visibleStart());
VisibleSelection oldAdjacentWords = VisibleSelection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary));
if (oldAdjacentWords != newAdjacentWords) {
@@ -3359,38 +3463,40 @@ void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, Fra
if (!isContinuousGrammarCheckingEnabled)
document().markers().removeMarkers(DocumentMarker::Grammar);
- notifyComponentsOnChangedSelection(oldSelection, options);
+ if (!m_editorUIUpdateTimerWasTriggeredByDictation)
+ m_alternativeTextController->respondToChangedSelection(oldSelection);
+
+ m_oldSelectionForEditorUIUpdate = m_frame.selection().selection();
}
static Node* findFirstMarkable(Node* node)
{
while (node) {
if (!node->renderer())
- return 0;
+ return nullptr;
if (node->renderer()->isTextOrLineBreak())
return node;
- if (isHTMLTextFormControlElement(*node))
- node = toHTMLTextFormControlElement(node)->visiblePositionForIndex(1).deepEquivalent().deprecatedNode();
+ if (is<HTMLTextFormControlElement>(*node))
+ node = downcast<HTMLTextFormControlElement>(*node).visiblePositionForIndex(1).deepEquivalent().deprecatedNode();
else if (node->firstChild())
node = node->firstChild();
else
node = node->nextSibling();
}
- return 0;
+ return nullptr;
}
bool Editor::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType, int from, int length) const
{
- Node* node = findFirstMarkable(m_frame.selection().start().deprecatedNode());
+ Node* node = findFirstMarkable(m_frame.selection().selection().start().deprecatedNode());
if (!node)
return false;
unsigned int startOffset = static_cast<unsigned int>(from);
unsigned int endOffset = static_cast<unsigned int>(from + length);
- Vector<DocumentMarker*> markers = document().markers().markersFor(node);
- for (size_t i = 0; i < markers.size(); ++i) {
- DocumentMarker* marker = markers[i];
+ Vector<RenderedDocumentMarker*> markers = document().markers().markersFor(node);
+ for (auto* marker : markers) {
if (marker->startOffset() <= startOffset && endOffset <= marker->endOffset() && marker->type() == markerType)
return true;
}
@@ -3398,8 +3504,18 @@ bool Editor::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType, i
return false;
}
-TextCheckingTypeMask Editor::resolveTextCheckingTypeMask(TextCheckingTypeMask textCheckingOptions)
+TextCheckingTypeMask Editor::resolveTextCheckingTypeMask(const Node& rootEditableElement, TextCheckingTypeMask textCheckingOptions)
{
+#if USE(AUTOMATIC_TEXT_REPLACEMENT) && !PLATFORM(IOS)
+ bool onlyAllowsTextReplacement = false;
+ if (auto* host = rootEditableElement.shadowHost())
+ onlyAllowsTextReplacement = is<HTMLInputElement>(host) && downcast<HTMLInputElement>(*host).isSpellcheckDisabledExceptTextReplacement();
+ if (onlyAllowsTextReplacement)
+ textCheckingOptions &= TextCheckingTypeReplacement;
+#else
+ UNUSED_PARAM(rootEditableElement);
+#endif
+
bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling;
bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
#if !PLATFORM(IOS)
@@ -3421,16 +3537,18 @@ TextCheckingTypeMask Editor::resolveTextCheckingTypeMask(TextCheckingTypeMask te
#if USE(AUTOMATIC_TEXT_REPLACEMENT)
bool shouldPerformReplacement = textCheckingOptions & TextCheckingTypeReplacement;
if (shouldPerformReplacement) {
- if (isAutomaticLinkDetectionEnabled())
- checkingTypes |= TextCheckingTypeLink;
- if (isAutomaticQuoteSubstitutionEnabled())
- checkingTypes |= TextCheckingTypeQuote;
- if (isAutomaticDashSubstitutionEnabled())
- checkingTypes |= TextCheckingTypeDash;
+ if (!onlyAllowsTextReplacement) {
+ if (isAutomaticLinkDetectionEnabled())
+ checkingTypes |= TextCheckingTypeLink;
+ if (isAutomaticQuoteSubstitutionEnabled())
+ checkingTypes |= TextCheckingTypeQuote;
+ if (isAutomaticDashSubstitutionEnabled())
+ checkingTypes |= TextCheckingTypeDash;
+ if (shouldMarkSpelling && isAutomaticSpellingCorrectionEnabled())
+ checkingTypes |= TextCheckingTypeCorrection;
+ }
if (isAutomaticTextReplacementEnabled())
checkingTypes |= TextCheckingTypeReplacement;
- if (shouldMarkSpelling && isAutomaticSpellingCorrectionEnabled())
- checkingTypes |= TextCheckingTypeCorrection;
}
#endif
#endif // !PLATFORM(IOS)
@@ -3438,11 +3556,68 @@ TextCheckingTypeMask Editor::resolveTextCheckingTypeMask(TextCheckingTypeMask te
return checkingTypes;
}
-void Editor::deviceScaleFactorChanged()
+static RefPtr<Range> candidateRangeForSelection(Frame& frame)
{
-#if ENABLE(DELETION_UI)
- m_deleteButtonController->deviceScaleFactorChanged();
-#endif
+ const VisibleSelection& selection = frame.selection().selection();
+ return selection.isCaret() ? wordRangeFromPosition(selection.start()) : frame.selection().toNormalizedRange();
+}
+
+static bool candidateWouldReplaceText(const VisibleSelection& selection)
+{
+ // If the character behind the caret in the current selection is anything but a space or a newline then we should
+ // replace the whole current word with the candidate.
+ UChar32 characterAfterSelection, characterBeforeSelection, twoCharacterBeforeSelection = 0;
+ charactersAroundPosition(selection.visibleStart(), characterAfterSelection, characterBeforeSelection, twoCharacterBeforeSelection);
+ return !(characterBeforeSelection == '\0' || characterBeforeSelection == '\n' || characterBeforeSelection == ' ');
+}
+
+String Editor::stringForCandidateRequest() const
+{
+ const VisibleSelection& selection = m_frame.selection().selection();
+ RefPtr<Range> rangeForCurrentlyTypedString = candidateRangeForSelection(m_frame);
+ if (rangeForCurrentlyTypedString && candidateWouldReplaceText(selection))
+ return plainText(rangeForCurrentlyTypedString.get());
+
+ return String();
+}
+
+RefPtr<Range> Editor::contextRangeForCandidateRequest() const
+{
+ const VisibleSelection& selection = m_frame.selection().selection();
+ return makeRange(startOfParagraph(selection.visibleStart()), endOfParagraph(selection.visibleEnd()));
+}
+
+RefPtr<Range> Editor::rangeForTextCheckingResult(const TextCheckingResult& result) const
+{
+ if (!result.length)
+ return nullptr;
+
+ RefPtr<Range> contextRange = contextRangeForCandidateRequest();
+ if (!contextRange)
+ return nullptr;
+
+ return TextIterator::subrange(contextRange.get(), result.location, result.length);
+}
+
+void Editor::handleAcceptedCandidate(TextCheckingResult acceptedCandidate)
+{
+ const VisibleSelection& selection = m_frame.selection().selection();
+
+ m_isHandlingAcceptedCandidate = true;
+
+ if (auto range = rangeForTextCheckingResult(acceptedCandidate)) {
+ if (shouldInsertText(acceptedCandidate.replacement, range.get(), EditorInsertAction::Typed)) {
+ Ref<ReplaceRangeWithTextCommand> replaceCommand = ReplaceRangeWithTextCommand::create(range.get(), acceptedCandidate.replacement);
+ applyCommand(replaceCommand.ptr());
+ }
+ } else
+ insertText(acceptedCandidate.replacement, nullptr);
+
+ RefPtr<Range> insertedCandidateRange = rangeExpandedByCharactersInDirectionAtWordBoundary(selection.visibleStart(), acceptedCandidate.replacement.length(), DirectionBackward);
+ if (insertedCandidateRange)
+ insertedCandidateRange->startContainer().document().markers().addMarker(insertedCandidateRange.get(), DocumentMarker::AcceptedCandidate, acceptedCandidate.replacement);
+
+ m_isHandlingAcceptedCandidate = false;
}
bool Editor::unifiedTextCheckerEnabled() const
@@ -3450,7 +3625,7 @@ bool Editor::unifiedTextCheckerEnabled() const
return WebCore::unifiedTextCheckerEnabled(&m_frame);
}
-Vector<String> Editor::dictationAlternativesForMarker(const DocumentMarker* marker)
+Vector<String> Editor::dictationAlternativesForMarker(const DocumentMarker& marker)
{
return m_alternativeTextController->dictationAlternativesForMarker(marker);
}
@@ -3472,4 +3647,108 @@ Document& Editor::document() const
return *m_frame.document();
}
+RefPtr<Range> Editor::adjustedSelectionRange()
+{
+ // FIXME: Why do we need to adjust the selection to include the anchor tag it's in?
+ // Whoever wrote this code originally forgot to leave us a comment explaining the rationale.
+ RefPtr<Range> range = selectedRange();
+ Node* commonAncestor = range->commonAncestorContainer();
+ ASSERT(commonAncestor);
+ auto* enclosingAnchor = enclosingElementWithTag(firstPositionInNode(commonAncestor), HTMLNames::aTag);
+ if (enclosingAnchor && comparePositions(firstPositionInOrBeforeNode(range->startPosition().anchorNode()), range->startPosition()) >= 0)
+ range->setStart(*enclosingAnchor, 0);
+ return range;
+}
+
+// FIXME: This figures out the current style by inserting a <span>!
+const RenderStyle* Editor::styleForSelectionStart(Frame* frame, Node*& nodeToRemove)
+{
+ nodeToRemove = nullptr;
+
+ if (frame->selection().isNone())
+ return nullptr;
+
+ Position position = adjustedSelectionStartForStyleComputation(frame->selection().selection());
+ if (!position.isCandidate() || position.isNull())
+ return nullptr;
+
+ RefPtr<EditingStyle> typingStyle = frame->selection().typingStyle();
+ if (!typingStyle || !typingStyle->style())
+ return &position.deprecatedNode()->renderer()->style();
+
+ auto styleElement = HTMLSpanElement::create(*frame->document());
+
+ String styleText = typingStyle->style()->asText() + " display: inline";
+ styleElement->setAttribute(HTMLNames::styleAttr, styleText);
+
+ styleElement->appendChild(frame->document()->createEditingTextNode(emptyString()));
+
+ auto positionNode = position.deprecatedNode();
+ if (!positionNode || !positionNode->parentNode() || positionNode->parentNode()->appendChild(styleElement).hasException())
+ return nullptr;
+
+ nodeToRemove = styleElement.ptr();
+
+ frame->document()->updateStyleIfNeeded();
+ return styleElement->renderer() ? &styleElement->renderer()->style() : nullptr;
+}
+
+const Font* Editor::fontForSelection(bool& hasMultipleFonts) const
+{
+ hasMultipleFonts = false;
+
+ if (!m_frame.selection().isRange()) {
+ Node* nodeToRemove;
+ auto* style = styleForSelectionStart(&m_frame, nodeToRemove); // sets nodeToRemove
+
+ const Font* font = nullptr;
+ if (style) {
+ font = &style->fontCascade().primaryFont();
+ if (nodeToRemove)
+ nodeToRemove->remove();
+ }
+
+ return font;
+ }
+
+ RefPtr<Range> range = m_frame.selection().toNormalizedRange();
+ if (!range)
+ return nullptr;
+
+ Node* startNode = adjustedSelectionStartForStyleComputation(m_frame.selection().selection()).deprecatedNode();
+ if (!startNode)
+ return nullptr;
+
+ const Font* font = nullptr;
+ Node* pastEnd = range->pastLastNode();
+ // In the loop below, node should eventually match pastEnd and not become null, but we've seen at least one
+ // unreproducible case where this didn't happen, so check for null also.
+ for (Node* node = startNode; node && node != pastEnd; node = NodeTraversal::next(*node)) {
+ auto renderer = node->renderer();
+ if (!renderer)
+ continue;
+ // FIXME: Are there any node types that have renderers, but that we should be skipping?
+ const Font& primaryFont = renderer->style().fontCascade().primaryFont();
+ if (!font)
+ font = &primaryFont;
+ else if (font != &primaryFont) {
+ hasMultipleFonts = true;
+ break;
+ }
+ }
+
+ return font;
+}
+
+Ref<DocumentFragment> Editor::createFragmentForImageAndURL(const String& url)
+{
+ auto imageElement = HTMLImageElement::create(*m_frame.document());
+ imageElement->setAttributeWithoutSynchronization(HTMLNames::srcAttr, url);
+
+ auto fragment = document().createDocumentFragment();
+ fragment->appendChild(imageElement);
+
+ return fragment;
+}
+
} // namespace WebCore