summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/input-events/input-events-get-target-ranges-deleting-range-across-editing-host-boundaries.tentative.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/input-events/input-events-get-target-ranges-deleting-range-across-editing-host-boundaries.tentative.html')
-rw-r--r--testing/web-platform/tests/input-events/input-events-get-target-ranges-deleting-range-across-editing-host-boundaries.tentative.html598
1 files changed, 598 insertions, 0 deletions
diff --git a/testing/web-platform/tests/input-events/input-events-get-target-ranges-deleting-range-across-editing-host-boundaries.tentative.html b/testing/web-platform/tests/input-events/input-events-get-target-ranges-deleting-range-across-editing-host-boundaries.tentative.html
new file mode 100644
index 0000000000..04f632a929
--- /dev/null
+++ b/testing/web-platform/tests/input-events/input-events-get-target-ranges-deleting-range-across-editing-host-boundaries.tentative.html
@@ -0,0 +1,598 @@
+<!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>