<!DOCTYPE html>
<meta charset="utf-8">
<title>InputEvent.getTargetRanges() of deleting a range across editing host boundaries</title>
<div contenteditable></div>
<script src="input-events-get-target-ranges.js"></script>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script>
"use strict";
// This test just check whether the deleted content range(s) and target ranges of `beforeinput`
// are match or different. The behavior should be defined by editing API.
// https://github.com/w3c/editing/issues/283
promise_test(async () => {
initializeTest('<p>ab[c<span contenteditable="false">no]n-editable</span>def</p>');
await sendBackspaceKey();
const kNothingDeletedCase = '<p>abc<span contenteditable="false">non-editable</span>def</p>';
const kOnlyEditableTextDeletedCase = '<p>ab<span contenteditable="false">non-editable</span>def</p>';
const kNonEditableElementDeleteCase = '<p>abdef</p>';
if (gEditor.innerHTML === kNothingDeletedCase) {
if (gBeforeinput.length === 0) {
assert_true(true, "If nothing changed, `beforeinput` event may not be fired");
assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired");
return;
}
assert_equals(gBeforeinput.length, 1,
"If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves");
assert_equals(gBeforeinput[0].cachedRanges.length, 0,
`If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${
getRangeDescription(gBeforeinput[0].cachedRanges[0])
})`);
assert_equals(gBeforeinput[0].inputType, "deleteContentBackward",
"If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward");
assert_equals(gInput.length, 0,
"If nothing changed but `beforeinput` event is fired, `input` event should not be fired");
return;
}
if (gEditor.innerHTML === kOnlyEditableTextDeletedCase) {
assert_equals(gBeforeinput.length, 1,
"If only editable text is deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If only editable text is deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild.firstChild,
startOffset: 2,
endContainer: gEditor.firstChild.firstChild,
endOffset: 3,
}),
"If only editable text is deleted, its target range should be the deleted text range");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If only editable text is deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If only editable text is deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kNonEditableElementDeleteCase) {
assert_equals(gBeforeinput.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
assert_in_array(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
[
getRangeDescription({
startContainer: gEditor.firstChild.firstChild,
startOffset: 2,
endContainer: gEditor.firstChild,
endOffset: 2,
}),
getRangeDescription({
startContainer: gEditor.firstChild.firstChild,
startOffset: 2,
endContainer: gEditor.firstChild.firstChild.nextSibling,
endOffset: 0,
}),
],
"If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text and non-editable element are deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text and non-editable element are deleted, `input` event should be fired");
return;
}
assert_in_array(gEditor.innerHTML,
[
kNothingDeletedCase,
kOnlyEditableTextDeletedCase,
kNonEditableElementDeleteCase,
], "The result content is unexpected");
}, 'Backspace at "<p>ab[c<span contenteditable="false">no]n-editable</span>def</p>"');
promise_test(async () => {
initializeTest('<p>abc<span contenteditable="false">non-[editable</span>de]f</p>');
await sendBackspaceKey();
const kNothingDeletedCase = '<p>abc<span contenteditable="false">non-editable</span>def</p>';
const kOnlyEditableTextDeletedCase = '<p>abc<span contenteditable="false">non-editable</span>f</p>';
const kNonEditableElementDeletedCase = '<p>abcf</p>';;
if (gEditor.innerHTML === kNothingDeletedCase) {
if (gBeforeinput.length === 0) {
assert_true(true, "If nothing changed, `beforeinput` event may not be fired");
assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired");
return;
}
assert_equals(gBeforeinput.length, 1,
"If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves");
assert_equals(gBeforeinput[0].cachedRanges.length, 0,
`If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${
getRangeDescription(gBeforeinput[0].cachedRanges[0])
})`);
assert_equals(gBeforeinput[0].inputType, "deleteContentBackward",
"If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward");
assert_equals(gInput.length, 0,
"If nothing changed but `beforeinput` event is fired, `input` event should not be fired");
return;
}
if (gEditor.innerHTML === kOnlyEditableTextDeletedCase) {
assert_equals(gBeforeinput.length, 1,
"If only editable text is deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If only editable text is deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild.lastChild,
startOffset: 0,
endContainer: gEditor.firstChild.lastChild,
endOffset: 2,
}),
"If only editable text is deleted, its target range should be the deleted text range");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If only editable text is deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If only editable text is deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kNonEditableElementDeletedCase) {
assert_equals(gBeforeinput.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild,
startOffset: 1,
endContainer: gEditor.firstChild.lastChild,
endOffset: 2,
}),
"If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text and non-editable element are deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text and non-editable element are deleted, `input` event should be fired");
return;
}
assert_in_array(gEditor.innerHTML,
[
kNothingDeletedCase,
kOnlyEditableTextDeletedCase,
kNonEditableElementDeletedCase,
], "The result content is unexpected");
}, 'Backspace at "<p>abc<span contenteditable="false">non-[editable</span>de]f</p>"');
promise_test(async () => {
initializeTest('<p contenteditable="false"><span contenteditable>a[bc</span>non-editable<span contenteditable>de]f</span></p>');
let firstRange = gSelection.getRangeAt(0);
if (!firstRange ||
firstRange.startContainer != gEditor.firstChild.firstChild.firstChild ||
firstRange.startOffset != 1 ||
firstRange.endContainer != gEditor.firstChild.lastChild.firstChild ||
firstRange.endOffset != 2) {
assert_true(true, "Selection couldn't set across editing host boundaries");
return;
}
await sendBackspaceKey();
const kNothingDeletedCase = '<p contenteditable="false"><span contenteditable="">abc</span>non-editable<span contenteditable="">def</span></p>';
const kOnlyEditableContentDeletedCase = '<p contenteditable="false"><span contenteditable="">a</span>non-editable<span contenteditable="">f</span></p>';
const kNonEditableElementDeletedCase = '<p contenteditable="false"><span contenteditable="">af</span></p>';
const kDeleteEditableContentBeforeNonEditableContentCase = '<p contenteditable="false"><span contenteditable="">a</span>non-editable<span contenteditable="">def</span></p>';
const kDeleteEditableContentAfterNonEditableContentCase = '<p contenteditable="false"><span contenteditable="">abc</span>non-editable<span contenteditable="">f</span></p>';
if (gEditor.innerHTML === kNothingDeletedCase) {
if (gBeforeinput.length === 0) {
assert_true(true, "If nothing changed, `beforeinput` event may not be fired");
assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired");
return;
}
assert_equals(gBeforeinput.length, 1,
"If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves");
assert_equals(gBeforeinput[0].cachedRanges.length, 0,
`If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${
getRangeDescription(gBeforeinput[0].cachedRanges[0])
})`);
assert_equals(gBeforeinput[0].inputType, "deleteContentBackward",
"If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward");
assert_equals(gInput.length, 0,
"If nothing changed but `beforeinput` event is fired, `input` event should not be fired");
return;
}
if (gEditor.innerHTML === kOnlyEditableContentDeletedCase) {
assert_equals(gBeforeinput.length, 1,
"If only editable text is deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 2,
"If only editable text is deleted, `beforeinput` event should have 2 target ranges");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild.firstChild.firstChild,
startOffset: 1,
endContainer: gEditor.lastChild,
endOffset: 3,
}),
"If only editable text is deleted, its first target range should be the deleted text range in the first text node");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[1]),
getRangeDescription({
startContainer: gEditor.firstChild.last.firstChild,
startOffset: 0,
endContainer: gEditor.firstChild.last.firstChild,
endOffset: 2,
}),
"If only editable text is deleted, its second target range should be the deleted text range in the last text node");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If only editable text is deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If only editable text is deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kNonEditableElementDeletedCase) {
assert_equals(gBeforeinput.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild.firstChild.firstChild,
startOffset: 1,
endContainer: gEditor.firstChild.lastChild.firstChild,
endOffset: 2,
}),
"If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text and non-editable element are deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text and non-editable element are deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kDeleteEditableContentBeforeNonEditableContentCase) {
assert_equals(gBeforeinput.length, 1,
"If editable text before non-editable element is deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text before non-editable element is deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild.firstChild.firstChild,
startOffset: 1,
endContainer: gEditor.firstChild.firstChild.firstChild,
endOffset: 3,
}),
"If editable text before non-editable element is deleted, its target range should be only the deleted text");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text before non-editable element is deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text before non-editable element is deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kDeleteEditableContentAfterNonEditableContentCase) {
assert_equals(gBeforeinput.length, 1,
"If editable text after non-editable element is deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text after non-editable element is deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild.lastChild.firstChild,
startOffset: 1,
endContainer: gEditor.firstChild.lastChild.firstChild,
endOffset: 3,
}),
"If editable text after non-editable element is deleted, its target range should be only the deleted text");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text after non-editable element is deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text after non-editable element is deleted, `input` event should be fired");
return;
}
assert_in_array(gEditor.innerHTML,
[
kNothingDeletedCase,
kOnlyEditableContentDeletedCase,
kNonEditableElementDeletedCase,
kDeleteEditableContentBeforeNonEditableContentCase,
kDeleteEditableContentAfterNonEditableContentCase,
], "The result content is unexpected");
}, 'Backspace at "<p contenteditable="false"><span contenteditable>a[bc</span>non-editable<span contenteditable>de]f</span></p>"');
promise_test(async () => {
initializeTest('<p>a[bc<span contenteditable="false">non-editable<span contenteditable>de]f</span></span></p>');
let firstRange = gSelection.getRangeAt(0);
if (!firstRange ||
firstRange.startContainer != gEditor.firstChild.firstChild ||
firstRange.startOffset != 1 ||
firstRange.endContainer != gEditor.querySelector("span span").firstChild ||
firstRange.endOffset != 2) {
assert_true(true, "Selection couldn't set across editing host boundaries");
return;
}
await sendBackspaceKey();
const kNothingDeletedCase = '<p>abc<span contenteditable="false">non-editable<span contenteditable="">def</span></span></p>';
const kOnlyEditableContentDeletedCase = '<p>a<span contenteditable="false">non-editable<span contenteditable="">f</span></span></p>';
const kNonEditableElementDeletedCase1 = '<p>af</p>';
const kNonEditableElementDeletedCase2 = '<p>a<span contenteditable="">f</span></p>';
const kDeleteEditableContentBeforeNonEditableContentCase ='<p>a<span contenteditable="false">non-editable<span contenteditable="">def</span></span></p>';
const kDeleteEditableContentAfterNonEditableContentCase ='<p>abc<span contenteditable="false">non-editable<span contenteditable="">f</span></span></p>';
if (gEditor.innerHTML === kNothingDeletedCase) {
if (gBeforeinput.length === 0) {
assert_true(true, "If nothing changed, `beforeinput` event may not be fired");
assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired");
return;
}
assert_equals(gBeforeinput.length, 1,
"If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves");
assert_equals(gBeforeinput[0].cachedRanges.length, 0,
`If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${
getRangeDescription(gBeforeinput[0].cachedRanges[0])
})`);
assert_equals(gBeforeinput[0].inputType, "deleteContentBackward",
"If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward");
assert_equals(gInput.length, 0,
"If nothing changed but `beforeinput` event is fired, `input` event should not be fired");
return;
}
if (gEditor.innerHTML === kOnlyEditableContentDeletedCase) {
assert_equals(gBeforeinput.length, 1,
"If only editable text is deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 2,
"If only editable text is deleted, `beforeinput` event should have 2 target ranges");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild.firstChild,
startOffset: 1,
endContainer: gEditor.firstChild.firstChild,
endOffset: 3,
}),
"If only editable text is deleted, its first target range should be the deleted text range in the first text node");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[1]),
getRangeDescription({
startContainer: gEditor.querySelector("span span").firstChild,
startOffset: 0,
endContainer: gEditor.querySelector("span span").firstChild,
endOffset: 2,
}),
"If only editable text is deleted, its second target range should be the deleted text range in the last text node");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If only editable text is deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If only editable text is deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kNonEditableElementDeletedCase1) {
assert_equals(gBeforeinput.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
// XXX If the text nodes are merged, we need to cache it for here.
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild.firstChild,
startOffset: 1,
endContainer: gEditor.firstChild.lastChild,
endOffset: 2,
}),
"If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text and non-editable element are deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text and non-editable element are deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kNonEditableElementDeletedCase2) {
assert_equals(gBeforeinput.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild,
startOffset: 1,
endContainer: gEditor.querySelector("span").firstChild,
endOffset: 2,
}),
"If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text and non-editable element are deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text and non-editable element are deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kDeleteEditableContentBeforeNonEditableContentCase) {
assert_equals(gBeforeinput.length, 1,
"If editable text before non-editable element is deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text before non-editable element is deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild.firstChild,
startOffset: 1,
endContainer: gEditor.firstChild.firstChild,
endOffset: 3,
}),
"If editable text before non-editable element is deleted, its target range should be only the deleted text");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text before non-editable element is deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text before non-editable element is deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kDeleteEditableContentAfterNonEditableContentCase) {
assert_equals(gBeforeinput.length, 1,
"If editable text after non-editable element is deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text after non-editable element is deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.querySelector("span").firstChild,
startOffset: 0,
endContainer: gEditor.querySelector("span").firstChild,
endOffset: 2,
}),
"If editable text after non-editable element is deleted, its target range should be only the deleted text");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text after non-editable element is deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text after non-editable element is deleted, `input` event should be fired");
return;
}
assert_in_array(gEditor.innerHTML,
[
kNothingDeletedCase,
kOnlyEditableContentDeletedCase,
kNonEditableElementDeletedCase1,
kNonEditableElementDeletedCase2,
kDeleteEditableContentBeforeNonEditableContentCase,
kDeleteEditableContentAfterNonEditableContentCase,
], "The result content is unexpected");
}, 'Backspace at "<p>a[bc<span contenteditable="false">non-editable<span contenteditable>de]f</span></span></p>"');
promise_test(async () => {
initializeTest('<p><span contenteditable="false"><span contenteditable>a[bc</span>non-editable</span>de]f</p>');
let firstRange = gSelection.getRangeAt(0);
if (!firstRange ||
firstRange.startContainer != gEditor.querySelector("span span").firstChild ||
firstRange.startOffset != 1 ||
firstRange.endContainer != gEditor.firstChild.lastChild.firstChild ||
firstRange.endOffset != 2) {
assert_true(true, "Selection couldn't set across editing host boundaries");
return;
}
await sendBackspaceKey();
const kNothingDeletedCase = '<p><span contenteditable="false"><span contenteditable="">abc</span>non-editable</span>def</p>';
const kOnlyEditableContentDeletedCase = '<p><span contenteditable="false"><span contenteditable="">a</span>non-editable</span>f</p>';
const kNonEditableElementDeletedCase1 = '<p><span contenteditable="false"><span contenteditable="">af</span></span></p>';
const kNonEditableElementDeletedCase2 = '<p><span contenteditable="false"><span contenteditable="">a</span></span>f</p>';
const kDeleteEditableContentBeforeNonEditableContentCase = '<p><span contenteditable="false"><span contenteditable="">a</span>non-editable</span>def</p>';
const kDeleteEditableContentAfterNonEditableContentCase = '<p><span contenteditable="false"><span contenteditable="">abc</span>non-editable</span>f</p>';
if (gEditor.innerHTML === kNothingDeletedCase) {
if (gBeforeinput.length === 0) {
assert_true(true, "If nothing changed, `beforeinput` event may not be fired");
assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired");
return;
}
assert_equals(gBeforeinput.length, 1,
"If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves");
assert_equals(gBeforeinput[0].cachedRanges.length, 0,
`If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${
getRangeDescription(gBeforeinput[0].cachedRanges[0])
})`);
assert_equals(gBeforeinput[0].inputType, "deleteContentBackward",
"If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward");
assert_equals(gInput.length, 0,
"If nothing changed but `beforeinput` event is fired, `input` event should not be fired");
return;
}
if (gEditor.innerHTML === kOnlyEditableContentDeletedCase) {
assert_equals(gBeforeinput.length, 1,
"If only editable text is deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 2,
"If only editable text is deleted, `beforeinput` event should have 2 target ranges");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.querySelector("span span").firstChild,
startOffset: 1,
endContainer: gEditor.querySelector("span span").firstChild,
endOffset: 3,
}),
"If only editable text is deleted, its first target range should be the deleted text range in the first text node");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[1]),
getRangeDescription({
startContainer: gEditor.firstChild.lastChild,
startOffset: 0,
endContainer: gEditor.firstChild.lastChild,
endOffset: 2,
}),
"If only editable text is deleted, its second target range should be the deleted text range in the last text node");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If only editable text is deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If only editable text is deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kNonEditableElementDeletedCase1) {
assert_equals(gBeforeinput.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
// XXX If the text nodes are merged, we need to cache it for here.
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.querySelector("span span").firstChild,
startOffset: 1,
endContainer: gEditor.querySelector("span span").lastChild,
endOffset: 2,
}),
"If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text and non-editable element are deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text and non-editable element are deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kNonEditableElementDeletedCase2) {
assert_equals(gBeforeinput.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.querySelector("span span").firstChild,
startOffset: 1,
endContainer: gEditor.firstChild.lastChild,
endOffset: 2,
}),
"If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text and non-editable element are deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text and non-editable element are deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kDeleteEditableContentBeforeNonEditableContentCase) {
assert_equals(gBeforeinput.length, 1,
"If editable text before non-editable element is deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text before non-editable element is deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.querySelector("span span").firstChild,
startOffset: 1,
endContainer: gEditor.querySelector("span span").firstChild,
endOffset: 3,
}),
"If editable text before non-editable element is deleted, its target range should be only the deleted text");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text before non-editable element is deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text before non-editable element is deleted, `input` event should be fired");
return;
}
if (gEditor.innerHTML === kDeleteEditableContentAfterNonEditableContentCase) {
assert_equals(gBeforeinput.length, 1,
"If editable text after non-editable element is deleted, `beforeinput` event should be fired");
assert_equals(gBeforeinput[0].cachedRanges.length, 1,
"If editable text after non-editable element is deleted, `beforeinput` event should have a target range");
assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
getRangeDescription({
startContainer: gEditor.firstChild.lastChild,
startOffset: 0,
endContainer: gEditor.firstChild.lastChild,
endOffset: 2,
}),
"If editable text after non-editable element is deleted, its target range should be only the deleted text");
assert_equals(gBeforeinput[0].inputType, "deleteContent",
"If editable text after non-editable element is deleted, its input type should be deleteContent");
assert_equals(gInput.length, 1,
"If editable text after non-editable element is deleted, `input` event should be fired");
return;
}
assert_in_array(gEditor.innerHTML,
[
kNothingDeletedCase,
kOnlyEditableContentDeletedCase,
kNonEditableElementDeletedCase1,
kNonEditableElementDeletedCase2,
kDeleteEditableContentBeforeNonEditableContentCase,
kDeleteEditableContentAfterNonEditableContentCase,
], "The result content is unexpected");
}, 'Backspace at "<p><span contenteditable="false"><span contenteditable>a[bc</span>non-editable</span>de]f</p>"');
</script>