зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1658702 - part 22: Get rid of wrong `MOZ_ASSERT`s 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:
Родитель
d3fe79f0e9
Коммит
a9cd962def
|
@ -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(
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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`);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче