598 lines
32 KiB
HTML
598 lines
32 KiB
HTML
<!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>
|