Bug 1663601 - Make RangeBoundaryBase::GetNextSiblingOfChildAtOffset() check whether mRef is nullptr or not r=mbrodesser

`RangeBoundaryBase` stores a previous sibling of child node at offset with
`mRef`.  Therefore, even if the callers check whether its instance points a
child node, `mRef` may be `nullptr` when it points first child of its container.
So, `GetNextSiblingOfChildAtOffset()` needs to handle the case.

This patch adds the crash case test into
`test_dom_input_event_on_htmleditor.html` because of a basic behavior.
For now, this patch adds 2 chunks which are coded with using same style as
previous ones.  However, the test should be redesigned later for making
non-dependency of each chunk guaranteed.  (The new chunks are completely
independent from previously running tests.)

Differential Revision: https://phabricator.services.mozilla.com/D89440
This commit is contained in:
Masayuki Nakano 2020-09-09 11:19:04 +00:00
parent 7433c1b34d
commit 8bb9ef510a
2 changed files with 63 additions and 0 deletions

View File

@ -124,6 +124,16 @@ class RangeBoundaryBase {
if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
return nullptr;
}
if (!mRef) {
MOZ_ASSERT(*Offset(OffsetFilter::kValidOffsets) == 0,
"invalid RangeBoundary");
nsIContent* firstChild = mParent->GetFirstChild();
if (NS_WARN_IF(!firstChild)) {
// Already referring the end of the container.
return nullptr;
}
return firstChild->GetNextSibling();
}
if (NS_WARN_IF(!mRef->GetNextSibling())) {
// Already referring the end of the container.
return nullptr;

View File

@ -1249,6 +1249,59 @@ async function runTests() {
checkTargetRanges(beforeInputEvent, selectionRanges);
ok(!inputEvent, `${aDescription}"input" event shouldn't have been fired at ${action}`);
// Deleting atomic content
editTarget.innerHTML =
'<img src=""data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAEElEQVR42mNgaGD4D8YwBgAw9AX9Y9zBwwAAAABJRU5ErkJggg==">';
editTarget.focus();
selection.collapse(editTarget, 0);
reset();
cancelBeforeInput = false;
action = 'typing "Delete" to delete the <img> element';
synthesizeKey("KEY_Delete", {}, aWindow);
is(editTarget.innerHTML, `<br>`,
`${aDescription}the <img> should be deleted and padding <br> element should've been inserted instead at ${action}`);
ok(beforeInputEvent, `${aDescription}"beforeinput" event should've been fired at ${action}`);
is(beforeInputEvent.cancelable, true, `${aDescription}"beforeinput" event for ${action} should be cancelable`);
is(beforeInputEvent.inputType, "deleteContentForward", `${aDescription}inputType of "beforeinput" event 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, [{startContainer: editTarget,
startOffset: 0,
endContainer: editTarget,
endOffset: 1}]);
ok(inputEvent, `${aDescription}"input" event should've been fired at ${action}`);
is(inputEvent.cancelable, false, `${aDescription}"input" event for ${action} should never be cancelable`);
is(inputEvent.inputType, "deleteContentForward", `${aDescription}inputType of "input" event should be "deleteContentForward"`);
is(inputEvent.data, null, `${aDescription}data of "input" event for ${action} should be null`);
is(inputEvent.dataTransfer, null, `${aDescription}dataTransfer of "input" event for ${action} should be null`);
checkTargetRanges(inputEvent, []);
editTarget.innerHTML =
'<img src=""data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAEElEQVR42mNgaGD4D8YwBgAw9AX9Y9zBwwAAAABJRU5ErkJggg==">';
editTarget.focus();
selection.collapse(editTarget, 1);
reset();
cancelBeforeInput = false;
action = 'typing "Backspace" to delete the <img> element';
synthesizeKey("KEY_Backspace", {}, aWindow);
is(editTarget.innerHTML, `<br>`,
`${aDescription}the <img> should be deleted and padding <br> element should've been inserted instead at ${action}`);
ok(beforeInputEvent, `${aDescription}"beforeinput" event should've been fired at ${action}`);
is(beforeInputEvent.cancelable, true, `${aDescription}"beforeinput" event for ${action} should be cancelable`);
is(beforeInputEvent.inputType, "deleteContentBackward", `${aDescription}inputType of "beforeinput" event 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, [{startContainer: editTarget,
startOffset: 0,
endContainer: editTarget,
endOffset: 1}]);
ok(inputEvent, `${aDescription}"input" event should've been fired at ${action}`);
is(inputEvent.cancelable, false, `${aDescription}"input" event for ${action} should never be cancelable`);
is(inputEvent.inputType, "deleteContentBackward", `${aDescription}inputType of "input" event should be "deleteContentBackward"`);
is(inputEvent.data, null, `${aDescription}data of "input" event for ${action} should be null`);
is(inputEvent.dataTransfer, null, `${aDescription}dataTransfer of "input" event for ${action} should be null`);
checkTargetRanges(inputEvent, []);
aWindow.removeEventListener("beforeinput", beforeInputHandler, true);
aWindow.removeEventListener("input", inputHandler, true);
}