Bug 1658702 - part 22: Get rid of wrong MOZ_ASSERTs in WSRunScanner::GetRangeExtendToContainInvisibleWhiteSpacesAtRangeBoundaries() r=m_kato

If deleting/replacing range selects a text node, it may shrink the range
to start and end of the text node.  So, the result may not be wider than
the original range.

This behavior is compatible with Blink.

Depends on D90639

Differential Revision: https://phabricator.services.mozilla.com/D90640
This commit is contained in:
Masayuki Nakano 2020-09-27 06:02:38 +00:00
parent d3fe79f0e9
commit a9cd962def
6 changed files with 133 additions and 13 deletions

View File

@ -4995,8 +4995,8 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges(
if (!aHTMLEditor.IsPlaintextEditor()) {
EditorDOMRange firstRange(aRangesToDelete.FirstRangeRef());
EditorDOMRange extendedRange = WSRunScanner::
GetRangeExtendToContainInvisibleWhiteSpacesAtRangeBoundaries(
EditorDOMRange extendedRange =
WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
aHTMLEditor, EditorDOMRange(aRangesToDelete.FirstRangeRef()));
if (firstRange != extendedRange) {
nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(

View File

@ -3451,7 +3451,7 @@ EditorDOMRange WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
// static
EditorDOMRange
WSRunScanner::GetRangeExtendToContainInvisibleWhiteSpacesAtRangeBoundaries(
WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange) {
MOZ_ASSERT(aRange.IsPositionedAndValid());
MOZ_ASSERT(aRange.EndRef().IsSetAndValid());
@ -3466,8 +3466,6 @@ WSRunScanner::GetRangeExtendToContainInvisibleWhiteSpacesAtRangeBoundaries(
textFragmentDataAtStart.InvisibleLeadingWhiteSpaceRangeRef());
if (invisibleLeadingWhiteSpacesAtStart.IsPositioned() &&
!invisibleLeadingWhiteSpacesAtStart.Collapsed()) {
MOZ_ASSERT(invisibleLeadingWhiteSpacesAtStart.StartRef().EqualsOrIsBefore(
aRange.StartRef()));
result.SetStart(invisibleLeadingWhiteSpacesAtStart.StartRef());
} else {
const EditorDOMRangeInTexts invisibleTrailingWhiteSpacesAtStart =
@ -3480,6 +3478,13 @@ WSRunScanner::GetRangeExtendToContainInvisibleWhiteSpacesAtRangeBoundaries(
aRange.StartRef()));
result.SetStart(invisibleTrailingWhiteSpacesAtStart.StartRef());
}
// If there is no invisible white-space and the line starts with a
// text node, shrink the range to start of the text node.
else if (!aRange.StartRef().IsInTextNode() &&
textFragmentDataAtStart.StartsFromBlockBoundary() &&
textFragmentDataAtStart.EndRef().IsInTextNode()) {
result.SetStart(textFragmentDataAtStart.EndRef());
}
}
if (!result.StartRef().IsSet()) {
result.SetStart(aRange.StartRef());
@ -3491,8 +3496,6 @@ WSRunScanner::GetRangeExtendToContainInvisibleWhiteSpacesAtRangeBoundaries(
textFragmentDataAtEnd.InvisibleTrailingWhiteSpaceRangeRef());
if (invisibleLeadingWhiteSpacesAtEnd.IsPositioned() &&
!invisibleLeadingWhiteSpacesAtEnd.Collapsed()) {
MOZ_ASSERT(aRange.EndRef().EqualsOrIsBefore(
invisibleLeadingWhiteSpacesAtEnd.EndRef()));
result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef());
} else {
const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd =
@ -3504,6 +3507,14 @@ WSRunScanner::GetRangeExtendToContainInvisibleWhiteSpacesAtRangeBoundaries(
invisibleLeadingWhiteSpacesAtEnd.EndRef()));
result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef());
}
// If there is no invisible white-space and the line ends with a text
// node, shrink the range to end of the text node.
else if (!aRange.EndRef().IsInTextNode() &&
textFragmentDataAtEnd.EndsByBlockBoundary() &&
textFragmentDataAtEnd.StartRef().IsInTextNode()) {
result.SetEnd(EditorDOMPoint::AtEndOf(
*textFragmentDataAtEnd.StartRef().ContainerAsText()));
}
}
if (!result.EndRef().IsSet()) {
result.SetEnd(aRange.EndRef());

View File

@ -403,11 +403,10 @@ class MOZ_STACK_CLASS WSRunScanner final {
const Element* aEditingHost);
/**
* GetRangeExtendToContainInvisibleWhiteSpacesAtRangeBoundaries() returns
* GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries() returns
* extended range if range boundaries of aRange are in invisible white-spaces.
*/
static EditorDOMRange
GetRangeExtendToContainInvisibleWhiteSpacesAtRangeBoundaries(
static EditorDOMRange GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange);
/**

View File

@ -84,6 +84,7 @@ async function runTests() {
let beforeInputEvent = null;
let inputEvent = null;
let selectionRanges = [];
let expectedTargetRanges = [];
function reset() {
beforeInputEvent = null;
inputEvent = null;
@ -500,6 +501,12 @@ async function runTests() {
selection.selectAllChildren(editTarget);
reset();
cancelBeforeInput = false;
expectedTargetRanges = [{
startContainer: editTarget.firstChild,
startOffset: 0,
endContainer: editTarget.firstChild,
endOffset: editTarget.firstChild.length,
}];
synthesizeKey("KEY_Backspace", {}, aWindow);
is(editTarget.innerHTML, "<br>", `${aDescription}"a" should've been removed by ${action}`);
ok(beforeInputEvent, `${aDescription}"beforeinput" event should've been fired at ${action}`);
@ -508,7 +515,7 @@ async function runTests() {
`${aDescription}inputType of "beforeinput" event for ${action} should be "deleteContentBackward"`);
is(beforeInputEvent.data, null, `${aDescription}data of "beforeinput" event for ${action} should be null`);
is(beforeInputEvent.dataTransfer, null, `${aDescription}dataTransfer of "beforeinput" event for ${action} should be null`);
checkTargetRanges(beforeInputEvent, selectionRanges);
checkTargetRanges(beforeInputEvent, expectedTargetRanges);
ok(inputEvent, `${aDescription}"input" event should've been fired at ${action}`);
is(inputEvent.inputType, "deleteContentBackward", `${aDescription}inputType of "input" event for ${action} should be "deleteContentBackward"`);
is(inputEvent.data, null, `${aDescription}data of "input" event for ${action} should be null`);
@ -521,6 +528,12 @@ async function runTests() {
reset();
cancelBeforeInput = true;
action = 'removing "a" with "Delete" (with selection)';
expectedTargetRanges = [{
startContainer: editTarget.firstChild,
startOffset: 0,
endContainer: editTarget.firstChild,
endOffset: editTarget.firstChild.length,
}];
synthesizeKey("KEY_Delete", {}, aWindow);
is(editTarget.innerHTML, "a", `${aDescription}"a" should've been removed by ${action} since "beforeinput" was canceled`);
ok(beforeInputEvent, `${aDescription}"beforeinput" event should be fired at ${action} even if it won't remove any content`);
@ -528,7 +541,7 @@ async function runTests() {
is(beforeInputEvent.inputType, "deleteContentForward", `${aDescription}inputType of "beforeinput" event for ${action} should be "deleteContentForward"`);
is(beforeInputEvent.data, null, `${aDescription}data of "beforeinput" event for ${action} should be null`);
is(beforeInputEvent.dataTransfer, null, `${aDescription}dataTransfer of "beforeinput" event for ${action} should be null`);
checkTargetRanges(beforeInputEvent, selectionRanges);
checkTargetRanges(beforeInputEvent, expectedTargetRanges);
ok(!inputEvent, `${aDescription}${action} should not fire "input" event since "beforeinput" was canceled`);
editTarget.innerHTML = "a";
@ -537,6 +550,12 @@ async function runTests() {
reset();
cancelBeforeInput = false;
action = 'removing "a" with "Delete" (with selection)';
expectedTargetRanges = [{
startContainer: editTarget.firstChild,
startOffset: 0,
endContainer: editTarget.firstChild,
endOffset: editTarget.firstChild.length,
}];
synthesizeKey("KEY_Delete", {}, aWindow);
is(editTarget.innerHTML, "<br>", `${aDescription}" " should've been removed by ${action}`);
ok(beforeInputEvent, `${aDescription}"beforeinput" event should've been fired at ${action}`);
@ -544,7 +563,7 @@ async function runTests() {
is(beforeInputEvent.inputType, "deleteContentForward", `${aDescription}inputType of "beforeinput" event for ${action} should be "deleteContentForward"`);
is(beforeInputEvent.data, null, `${aDescription}data of "beforeinput" event for ${action} should be null`);
is(beforeInputEvent.dataTransfer, null, `${aDescription}dataTransfer of "beforeinput" event for ${action} should be null`);
checkTargetRanges(beforeInputEvent, selectionRanges);
checkTargetRanges(beforeInputEvent, expectedTargetRanges);
ok(inputEvent, `${aDescription}"input" event should've been fired at ${action}`);
is(inputEvent.inputType, "deleteContentForward", `${aDescription}inputType of "input" event for ${action} should be "deleteContentForward"`);
is(inputEvent.data, null, `${aDescription}data of "input" event for ${action} should be null`);

View File

@ -2,6 +2,9 @@
max-asserts: 11 # The assertion in nsTableCellFrame::DecorateForSelection() hits randomly.
min-asserts: 0
prefs: [editor.hr_element.allow_to_delete_from_following_line:true]
[Backspace at "<p>{abc}<br></p>"]
expected: FAIL
[Backspace at "<div>abc [<ul><li>\] def </li></ul> ghi</div>"]
expected: FAIL
@ -12,6 +15,9 @@
[input-events-get-target-ranges-non-collapsed-selection.tentative.html?Delete]
max-asserts: 11 # The assertion in nsTableCellFrame::DecorateForSelection() hits randomly.
min-asserts: 0
[Delete at "<p>{abc}<br></p>"]
expected: FAIL
[Delete at "<p>abc[</p><p>}<br></p>"]
expected: FAIL
@ -25,6 +31,9 @@
[input-events-get-target-ranges-non-collapsed-selection.tentative.html?TypingA]
max-asserts: 11 # The assertion in nsTableCellFrame::DecorateForSelection() hits randomly.
min-asserts: 0
[TypingA at "<p>{abc}<br></p>"]
expected: FAIL
[TypingA at "<div>abc [<ul><li>\] def </li></ul> ghi</div>"]
expected: FAIL

View File

@ -31,6 +31,88 @@ function run() {
let insertedHTML = action === "TypingA" ? "a" : "";
// If text node is selected, target range should be shrunken to the edge of
// text node.
promise_test(async () => {
initializeTest("<p>abc</p>");
let p = gEditor.firstChild;
let abc = p.firstChild;
gSelection.setBaseAndExtent(p, 0, p, 1);
await run();
assert_equals(gEditor.innerHTML, `<p>${insertedHTML !== "" ? insertedHTML : "<br>"}</p>`);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 0,
endContainer: abc,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, `${action} at "<p>{abc}</p>"`);
promise_test(async () => {
initializeTest("<p>abc<br></p>");
let p = gEditor.firstChild;
let abc = p.firstChild;
gSelection.setBaseAndExtent(p, 0, p, 1);
await run();
assert_in_array(gEditor.innerHTML, [`<p>${insertedHTML !== "" ? insertedHTML : "<br>"}</p>`,
`<p>${insertedHTML}<br></p>`]);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 0,
endContainer: abc,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, `${action} at "<p>{abc}<br></p>"`);
promise_test(async () => {
initializeTest(`<p><img src="${kImgSrc}"></p>`);
let p = gEditor.firstChild;
gSelection.setBaseAndExtent(p, 0, p, 1);
await run();
assert_equals(gEditor.innerHTML, `<p>${insertedHTML !== "" ? insertedHTML : "<br>"}</p>`);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p,
startOffset: 0,
endContainer: p,
endOffset: 1,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, `${action} at "<p>{<img>}</p>"`);
promise_test(async () => {
initializeTest(`<p><img src="${kImgSrc}"><br></p>`);
let p = gEditor.firstChild;
gSelection.setBaseAndExtent(p, 0, p, 1);
await run();
assert_in_array(gEditor.innerHTML, [`<p>${insertedHTML !== "" ? insertedHTML : "<br>"}</p>`,
`<p>${insertedHTML}<br></p>`]);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p,
startOffset: 0,
endContainer: p,
endOffset: 1,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, `${action} at "<p>{<img>}<br></p>"`);
promise_test(async () => {
initializeTest("<p> abc </p>");
let p = gEditor.firstChild;
let abc = p.firstChild;
gSelection.setBaseAndExtent(p, 0, p, 1);
await run();
assert_equals(gEditor.innerHTML, `<p>${insertedHTML !== "" ? insertedHTML : "<br>"}</p>`);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 0,
endContainer: abc,
endOffset: 5,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, `${action} at "<p>{ abc }</p>"`);
// Invisible leading white-spaces in current block and invisible trailing
// white-spaces in the previous block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be