Bug 1449831 - part 3: Make editor set target ranges of `beforeinput` event if the editor instance is an `HTMLEditor` r=smaug

In most cases, `InputEvent.getTargetRange()` of `beforeinput` event should
return `Selection` ranges at dispatching the event.

This patch also handles special cases.
* composition change - target range should be the previous composition string
  which will be replaced with new composition string.
* replace text - target range should be the replace range.  This is used by
  spellchecker.
* drop - target range should be the drop point.

However, the other exception is not handled by this patch.  That is, deletions.
The target range(s) should be the range(s) which will be removed.  In most
cases, they also matches selection ranges, but may be extended to:
* surrogate pair boundary
* grapheme cluster boundary like complex emoji
* word/line deletion deletion
* `Backspace` or `Delete` from collapsed selection
* to end of unnecessary whitespaces

For supporting these cases, we need to separate
`HTMLEditor::HandleDeleteSelection()` and its helper methods and helper class
to range computation part and modifying the DOM tree part.  Of course, it
requires big changes and `InputEvent.getTargetRanges()` may be important for
feature detection of `beforeinput` event so that we should put off the big
changes to bug 1618457.

Differential Revision: https://phabricator.services.mozilla.com/D64730

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2020-03-03 03:39:26 +00:00
Родитель 0af4f88f9d
Коммит f5af8613ec
6 изменённых файлов: 123 добавлений и 7 удалений

Просмотреть файл

@ -61,7 +61,8 @@
#include "mozilla/dom/EventTarget.h" // for EventTarget
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/Selection.h" // for Selection, etc.
#include "mozilla/dom/Selection.h" // for Selection, etc.
#include "mozilla/dom/StaticRange.h" // for StaticRange
#include "mozilla/dom/Text.h"
#include "mozilla/dom/Event.h"
#include "nsAString.h" // for nsAString::Length, etc.
@ -5757,6 +5758,11 @@ void EditorBase::AutoEditActionDataSetter::InitializeDataTransferWithClipboard(
true /* is external */, aClipboardType);
}
void EditorBase::AutoEditActionDataSetter::AppendTargetRange(
StaticRange& aTargetRange) {
mTargetRanges.AppendElement(aTargetRange);
}
nsresult EditorBase::AutoEditActionDataSetter::MaybeDispatchBeforeInputEvent() {
MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
"We've already handled beforeinput event");
@ -5799,11 +5805,37 @@ nsresult EditorBase::AutoEditActionDataSetter::MaybeDispatchBeforeInputEvent() {
return NS_ERROR_FAILURE;
}
OwningNonNull<TextEditor> textEditor = *mEditorBase.AsTextEditor();
EditorInputType inputType = ToInputType(mEditAction);
// If mTargetRanges has not been initialized yet, it means that we may need
// to set it to selection ranges.
if (textEditor->AsHTMLEditor() && mTargetRanges.IsEmpty() &&
MayHaveTargetRangesOnHTMLEditor(inputType)) {
if (uint32_t rangeCount = textEditor->SelectionRefPtr()->RangeCount()) {
mTargetRanges.SetCapacity(rangeCount);
for (uint32_t i = 0; i < rangeCount; i++) {
nsRange* range = textEditor->SelectionRefPtr()->GetRangeAt(i);
if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) {
continue;
}
// Now, we need to fix the offset of target range because it may
// be referred after modifying the DOM tree and range boundaries
// of `range` may have not computed offset yet.
RefPtr<StaticRange> targetRange = StaticRange::Create(
range->GetStartContainer(), range->StartOffset(),
range->GetEndContainer(), range->EndOffset(), IgnoreErrors());
if (NS_WARN_IF(!targetRange) ||
NS_WARN_IF(!targetRange->IsPositioned())) {
continue;
}
mTargetRanges.AppendElement(std::move(targetRange));
}
}
}
nsEventStatus status = nsEventStatus_eIgnore;
nsresult rv = nsContentUtils::DispatchInputEvent(
targetElement, eEditorBeforeInput, ToInputType(mEditAction), textEditor,
mDataTransfer ? InputEventOptions(mDataTransfer)
: InputEventOptions(mData),
targetElement, eEditorBeforeInput, inputType, textEditor,
mDataTransfer ? InputEventOptions(mDataTransfer, std::move(mTargetRanges))
: InputEventOptions(mData, std::move(mTargetRanges)),
&status);
if (NS_WARN_IF(mEditorBase.Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;

Просмотреть файл

@ -9,6 +9,7 @@
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
#include "mozilla/EditAction.h" // for EditAction and EditSubAction
#include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint
#include "mozilla/EventForwards.h" // for InputEventTargetRanges
#include "mozilla/Maybe.h" // for Maybe
#include "mozilla/OwningNonNull.h" // for OwningNonNull
#include "mozilla/PresShell.h" // for PresShell
@ -928,6 +929,13 @@ class EditorBase : public nsIEditor,
SettingDataTransfer aSettingDataTransfer, int32_t aClipboardType);
dom::DataTransfer* GetDataTransfer() const { return mDataTransfer; }
/**
* AppendTargetRange() appends aTargetRange to target ranges. This should
* be used only by edit action handlers which do not want to set target
* ranges to selection ranges.
*/
void AppendTargetRange(dom::StaticRange& aTargetRange);
void Abort() { mAborted = true; }
bool IsAborted() const { return mAborted; }
@ -1106,6 +1114,9 @@ class EditorBase : public nsIEditor,
// The dataTransfer should be set to InputEvent.dataTransfer.
RefPtr<dom::DataTransfer> mDataTransfer;
// They are used for result of InputEvent.getTargetRanges() of beforeinput.
OwningNonNullStaticRangeArray mTargetRanges;
// Start point where spell checker should check from. This is used only
// by TextEditor.
EditorDOMPoint mSpellCheckRestartPoint;

Просмотреть файл

@ -28,6 +28,7 @@
#include "mozilla/dom/Event.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/StaticRange.h"
#include "nsAString.h"
#include "nsCRT.h"
#include "nsCaret.h"
@ -645,6 +646,12 @@ nsresult TextEditor::DeleteSelectionAsAction(EDirection aDirection,
}
}
// TODO: If we're an HTMLEditor instance, we need to compute delete ranges
// here. However, it means that we need to pick computation codes
// which are in `HandleDeleteSelection()`, its helper methods and
// `WSRunObject` so that we need to redesign `HandleDeleteSelection()`
// in bug 1618457.
nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
if (rv == NS_ERROR_EDITOR_ACTION_CANCELED || NS_WARN_IF(NS_FAILED(rv))) {
return EditorBase::ToGenericNSResult(rv);
@ -926,6 +933,7 @@ nsresult TextEditor::DeleteSelectionAndPrepareToCreateNode() {
nsresult TextEditor::SetTextAsAction(const nsAString& aString,
nsIPrincipal* aPrincipal) {
MOZ_ASSERT(aString.FindChar(nsCRT::CR) == kNotFound);
MOZ_ASSERT(!AsHTMLEditor());
AutoEditActionDataSetter editActionData(*this, EditAction::eSetText,
aPrincipal);
@ -947,12 +955,43 @@ nsresult TextEditor::ReplaceTextAsAction(const nsAString& aString,
AutoEditActionDataSetter editActionData(*this, EditAction::eReplaceText,
aPrincipal);
if (NS_WARN_IF(!editActionData.CanHandle())) {
return NS_ERROR_NOT_INITIALIZED;
}
if (!AsHTMLEditor()) {
editActionData.SetData(aString);
} else {
editActionData.InitializeDataTransfer(aString);
RefPtr<StaticRange> targetRange;
if (aReplaceRange) {
// Compute offset of the range before dispatching `beforeinput` event
// because it may be referred after the DOM tree is changed and the
// range may have not computed the offset yet.
targetRange = StaticRange::Create(
aReplaceRange->GetStartContainer(), aReplaceRange->StartOffset(),
aReplaceRange->GetEndContainer(), aReplaceRange->EndOffset(),
IgnoreErrors());
NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
"The aReplaceRange isn't valid");
} else {
Element* editingHost = AsHTMLEditor()->GetActiveEditingHost();
NS_WARNING_ASSERTION(editingHost,
"No active editing host, no target ranges");
if (editingHost) {
targetRange = StaticRange::Create(
editingHost, 0, editingHost, editingHost->Length(), IgnoreErrors());
NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
"Failed to create static range to replace all "
"contents in editing host");
}
}
if (targetRange && targetRange->IsPositioned()) {
editActionData.AppendTargetRange(*targetRange);
}
}
nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
if (rv == NS_ERROR_EDITOR_ACTION_CANCELED || NS_WARN_IF(NS_FAILED(rv))) {
return EditorBase::ToGenericNSResult(rv);
}
@ -1161,6 +1200,23 @@ nsresult TextEditor::OnCompositionChange(
editActionData.SetData(aCompositionChangeEvent.mData);
}
// If we're an `HTMLEditor` and this is second or later composition change,
// we should set target range to the range of composition string.
// Otherwise, set target ranges to selection ranges (will be done by
// editActionData itself before dispatching `beforeinput` event).
if (AsHTMLEditor() && mComposition->GetContainerTextNode()) {
RefPtr<StaticRange> targetRange = StaticRange::Create(
mComposition->GetContainerTextNode(),
mComposition->XPOffsetInTextNode(),
mComposition->GetContainerTextNode(),
mComposition->XPEndOffsetInTextNode(), IgnoreErrors());
NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
"mComposition has broken range information");
if (targetRange && targetRange->IsPositioned()) {
editActionData.AppendTargetRange(*targetRange);
}
}
// TODO: We need to use different EditAction value for beforeinput event
// if the event is followed by "compositionend" because corresponding
// "input" event will be fired from OnCompositionEnd() later with

Просмотреть файл

@ -12,6 +12,7 @@
#include "mozilla/dom/DataTransfer.h"
#include "mozilla/dom/DragEvent.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/StaticRange.h"
#include "nsAString.h"
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
@ -463,6 +464,15 @@ nsresult TextEditor::OnDrop(DragEvent* aDropEvent) {
}
} else {
editActionData.InitializeDataTransfer(dataTransfer);
RefPtr<StaticRange> targetRange = StaticRange::Create(
droppedAt.GetContainer(), droppedAt.Offset(), droppedAt.GetContainer(),
droppedAt.Offset(), IgnoreErrors());
NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
"Why did we fail to create collapsed static range at "
"dropped position?");
if (targetRange && targetRange->IsPositioned()) {
editActionData.AppendTargetRange(*targetRange);
}
nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
if (rv == NS_ERROR_EDITOR_ACTION_CANCELED || NS_WARN_IF(NS_FAILED(rv))) {
return EditorBase::ToGenericNSResult(rv);

Просмотреть файл

@ -391,6 +391,13 @@ Command GetInternalCommand(const char* aCommandName,
namespace mozilla {
template <class T>
class OwningNonNull;
namespace dom {
class StaticRange;
}
#define NS_EVENT_CLASS(aPrefix, aName) class aPrefix##aName;
#define NS_ROOT_EVENT_CLASS(aPrefix, aName) NS_EVENT_CLASS(aPrefix, aName)
@ -426,6 +433,8 @@ struct TextRange;
class EditCommands;
class TextRangeArray;
typedef nsTArray<OwningNonNull<dom::StaticRange>> OwningNonNullStaticRangeArray;
// FontRange.h
struct FontRange;

Просмотреть файл

@ -1196,8 +1196,6 @@ class WidgetSelectionEvent : public WidgetGUIEvent {
* mozilla::InternalEditorInputEvent
******************************************************************************/
typedef nsTArray<OwningNonNull<dom::StaticRange>> OwningNonNullStaticRangeArray;
class InternalEditorInputEvent : public InternalUIEvent {
private:
InternalEditorInputEvent()