Bug 1633797 - Make callers of `EditorBase::DeleteSelectionWithTransaction()` stop calling it if selection is collapsed but the caller tries to delete non-collapsed range r=m_kato

Our editor's deletion code removes nodes step-by-step.  Therefore, even when
somebodies call `DeleteSelectionWithTransaction()` for removing non-collapsed
ranges, they may have already removed all contents in the range.  In such
case, all callers shouldn't call `DeleteSelectionWithTransaction()`.

This makes `test_bug1425997.html` allow to run nexted `execCommand`.  It'll be
disabled even in the release channel, but we should keep testing it for
detecting bug of edge cases (like this bug).  Note that all crashtests which
test nested `execCommand` calls run with allowing it with the pref for same
reason.

Differential Revision: https://phabricator.services.mozilla.com/D73402
This commit is contained in:
Masayuki Nakano 2020-05-07 01:23:21 +00:00
Родитель 7edac7d87c
Коммит 214c31ebcb
3 изменённых файлов: 30 добавлений и 13 удалений

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

@ -4061,17 +4061,21 @@ nsresult EditorBase::DeleteSelectionWithTransaction(
return NS_ERROR_EDITOR_DESTROYED;
}
HowToHandleCollapsedRange howToHandleCollapsedRange =
EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount);
if (NS_WARN_IF(!SelectionRefPtr()->RangeCount()) ||
NS_WARN_IF(
SelectionRefPtr()->IsCollapsed() &&
EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount) ==
HowToHandleCollapsedRange::Ignore)) {
NS_WARN_IF(SelectionRefPtr()->IsCollapsed() &&
howToHandleCollapsedRange ==
HowToHandleCollapsedRange::Ignore)) {
NS_ASSERTION(
false,
"For avoiding to throw incompatible exception for `execCommand`, fix "
"the caller");
return NS_ERROR_FAILURE;
}
RefPtr<EditAggregateTransaction> deleteSelectionTransaction =
CreateTransactionForDeleteSelection(
EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount));
CreateTransactionForDeleteSelection(howToHandleCollapsedRange);
if (!deleteSelectionTransaction) {
NS_WARNING("EditorBase::CreateTransactionForDeleteSelection() failed");
return NS_ERROR_FAILURE;

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

@ -2359,9 +2359,13 @@ EditActionResult HTMLEditor::HandleDeleteSelection(
"HTMLEditor::HandleDeleteSelectionInternal() failed");
return result;
}
if (!result.Handled()) {
// If it's just ignored, we should fall this back to
// `DeleteSelectionWithTransaction()`.
// If it's just ignored, we should fall this back to
// `DeleteSelectionWithTransaction()` when selection is not collapsed or
// content around collapsed range should be deleted.
if (!result.Handled() && SelectionRefPtr()->RangeCount() > 0 &&
(!SelectionRefPtr()->IsCollapsed() ||
EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount) !=
HowToHandleCollapsedRange::Ignore)) {
nsresult rv =
DeleteSelectionWithTransaction(aDirectionAndAmount, aStripWrappers);
if (rv == NS_ERROR_EDITOR_DESTROYED) {
@ -2375,6 +2379,9 @@ EditActionResult HTMLEditor::HandleDeleteSelection(
"EditorBase::DeleteSelectionWithTransaction() failed, but ignored");
}
// XXX At here, selection may have no range because of mutation event
// listeners can do anything so that we should just return NS_OK instead
// of returning error.
EditorDOMPoint atNewStartOfSelection(
EditorBase::GetStartPoint(*SelectionRefPtr()));
if (NS_WARN_IF(!atNewStartOfSelection.IsSet())) {
@ -3212,7 +3219,10 @@ EditActionResult HTMLEditor::HandleDeleteNonCollapsedSelection(
// XXX This is odd. We do we simply use `DeleteSelectionWithTransaction()`
// only when **first** range is in same container?
if (firstRangeStart.GetContainer() == firstRangeEnd.GetContainer()) {
{
// Because of previous DOM tree changes, the range may be collapsed.
// If we've already removed all contents in the range, we shouldn't
// delete anything around the caret.
if (firstRangeStart != firstRangeEnd) {
AutoTrackDOMPoint startTracker(RangeUpdaterRef(), &firstRangeStart);
AutoTrackDOMPoint endTracker(RangeUpdaterRef(), &firstRangeEnd);
@ -3223,6 +3233,8 @@ EditActionResult HTMLEditor::HandleDeleteNonCollapsedSelection(
return EditActionHandled(rv);
}
}
// However, even if the range is removed, we may need to clean up the
// containers which become empty.
nsresult rv = DeleteUnnecessaryNodesAndCollapseSelection(
aDirectionAndAmount, firstRangeStart, firstRangeEnd);
NS_WARNING_ASSERTION(
@ -3464,7 +3476,7 @@ nsresult HTMLEditor::DeleteUnnecessaryNodesAndCollapseSelection(
EditorDOMPoint selectionEndPoint(aSelectionEndPoint);
// If we're handling D&D, this is called to delete dragging item from the
// tree. In this case, we should move parent blocks if it becomes empty.
// tree. In this case, we should remove parent blocks if it becomes empty.
if (GetEditAction() == EditAction::eDrop ||
GetEditAction() == EditAction::eDeleteByDrag) {
MOZ_ASSERT((atCaret.GetContainer() == selectionEndPoint.GetContainer() &&

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

@ -26,8 +26,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1425997
SimpleTest.waitForExplicitFinish();
// 3 assertions are recorded due to nested execCommand() but not a problem.
// They are necessary to detect invalid method call without mutation event listers.
SimpleTest.expectAssertions(0, 3);
SimpleTest.waitForFocus(function() {
SimpleTest.expectAssertions(3, 3);
SimpleTest.waitForFocus(async function() {
await SpecialPowers.pushPrefEnv({set: [["dom.document.exec_command.nested_calls_allowed", true]]});
let selection = window.getSelection();
let editor = document.getElementById("editor");
function onCharacterDataModified() {