зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
0af4f88f9d
Коммит
f5af8613ec
|
@ -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()
|
||||
|
|
Загрузка…
Ссылка в новой задаче