Bug 1655393 - Add WPT for `InputEvent.getTargetRanges()` r=smaug

There are no automated tests for `InputEvent.getTargetRanges()` because it
is set only when `beforeinput` event, but it's defined as not dispatched by
`document.execCommand`.

However, we can synthesize `beforeinput` event with test driver.

On the other hand, the definition in Input Events spec is not clear.
Therefore, most of the tests won't be passed on any browsers for now.
There are some spec issues which I filed:
* https://github.com/w3c/input-events/issues/112
* https://github.com/w3c/input-events/issues/113
* https://github.com/w3c/input-events/issues/114

These new test must be useful when browser vendors discuss the issues.

Differential Revision: https://phabricator.services.mozilla.com/D85527
This commit is contained in:
Masayuki Nakano 2020-08-04 03:33:46 +00:00
Родитель 804e3ce7de
Коммит e7e1616f73
4 изменённых файлов: 1793 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,86 @@
[input-events-get-target-ranges-backspace.tentative.html]
[Shift + Backspace at "<p>abc def[\] ghi</p>"]
expected: FAIL
[Control + Backspace at "<p>abc def[\] ghi</p>"]
expected:
if (os == "win") or (os == "android"): FAIL
PASS
[Alt + Backspace at "<p>abc def[\] ghi</p>"]
expected:
if (os == "mac") or (os == "win") or (os == "android"): FAIL
PASS
[Meta + Backspace at "<p> abc[\] def</p>"]
expected: FAIL
[Shift + Backspace at "<p> abc[\] def</p>"]
expected: FAIL
[Control + Backspace at "<p> abc[\] def</p>"]
expected: FAIL
[Alt + Backspace at "<p> abc[\] def</p>"]
expected: FAIL
[Backspace at "<p>abc </p><p>[\] def</p>"]
expected: FAIL
[Backspace at "<p>abc </p><pre>[\] def</pre>"]
expected: FAIL
[Backspace at "<div>abc <p> [\]def<br>ghi</p></div>"]
expected: FAIL
[Backspace at "<p>a[\]bc</p>"]
expected: FAIL
[Backspace at "<p>abc </p><p> [\]def</p>"]
expected: FAIL
[Backspace at "<p> a[\]bc</p>"]
expected: FAIL
[Backspace at "<p>a<span>b[\]</span>c</p>"]
expected: FAIL
[Backspace at "<p>abc<br><br></p><p>[\]def</p>"]
expected: FAIL
[Backspace at "<pre>abc </pre><p> [\]def </p>"]
expected: FAIL
[Backspace at "<p>abc[\]</p>"]
expected: FAIL
[Backspace at "<p>abc<br>[\]def</p>"]
expected: FAIL
[Backspace at "<p>ab[\]c</p>"]
expected: FAIL
[Backspace at "<p>abc{<br>}def</p>"]
expected: FAIL
[Backspace at "<p>abc </p><p> [\] def</p>"]
expected: FAIL
[Backspace at "<p>abc<br></p><p>[\]def</p>"]
expected: FAIL
[Backspace at "<p>abc [</p><p>\] def</p>"]
expected: FAIL
[Backspace at "<div><p>abc </p> [\]def</div>"]
expected: FAIL
[Backspace at "<p>abc </p><p> [\] def</p>"]
expected: FAIL
[Backspace at "<p>a<span>b</span>[\]c</p>"]
expected: FAIL
[Backspace at "<pre>abc </pre><p> [\]def</p>"]
expected: FAIL

Просмотреть файл

@ -0,0 +1,88 @@
[input-events-get-target-ranges-forwarddelete.tentative.html]
[Shift + Delete at "<p>abc [\]def ghi</p>"]
expected:
if (os == "mac") or (os == "win"): FAIL
PASS
[Control + Delete at "<p>abc [\]def ghi</p>"]
expected:
if (os == "win") or (os == "android"): FAIL
PASS
[Alt + Delete at "<p>abc [\]def ghi</p>"]
expected:
if (os == "mac") or (os == "android"): FAIL
PASS
[Meta + Delete at "<p>abc [\]def</p>"]
expected: FAIL
[Shift + Delete at "<p>abc [\]def </p>"]
expected: FAIL
[Alt + Delete at "<p>abc [\]def </p>"]
expected: FAIL
[Control + Delete at "<p>abc [\]def </p>"]
expected: FAIL
[Delete at "<p>[\]abc</p>"]
expected: FAIL
[Delete at "<p>a<span>[\]b</span>c</p>"]
expected: FAIL
[Delete at "<p>a[\]bc</p>"]
expected: FAIL
[Delete at "<pre>abc [\]</pre><p> def</p>"]
expected: FAIL
[Delete at "<div>abc[\] <p> def<br>ghi</p></div>"]
expected: FAIL
[Delete at "<p>abc [\] </p><p> def</p>"]
expected: FAIL
[Delete at "<pre>abc [\]</pre><p> def </p>"]
expected: FAIL
[Delete at "<p>ab[\]c </p>"]
expected: FAIL
[Delete at "<p>abc [\]</p><p> def</p>"]
expected: FAIL
[Delete at "<p>a[\]<span>b</span>c</p>"]
expected: FAIL
[Delete at "<p>abc[\] </p><p> def</p>"]
expected: FAIL
[Delete at "<p>ab[\]c</p>"]
expected: FAIL
[Delete at "<p>abc[\]<br></p><p>def</p>"]
expected: FAIL
[Delete at "<p>abc[\]<br>def</p>"]
expected: FAIL
[Delete at "<p>abc<br>{}<br></p><p>def</p>"]
expected: FAIL
[Delete at "<p>abc [\] </p><p> def</p>"]
expected: FAIL
[Delete at "<p>abc[\] </p><pre> def</pre>"]
expected: FAIL
[Delete at "<p>abc{<br>}def</p>"]
expected: FAIL
[Delete at "<p>abc [</p><p>\] def</p>"]
expected: FAIL
[Delete at "<div><p>abc[\] </p> def</div>"]
expected: FAIL

Просмотреть файл

@ -0,0 +1,811 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>InputEvent.getTargetRanges() at Backspace</title>
<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>
<div contenteditable></div>
<script>
const kBackspaceKey = "\uE003";
const kShift = "\uE008";
const kMeta = "\uE03d";
const kControl = "\uE009";
const kAlt = "\uE00A";
let selection = getSelection();
let editor = document.querySelector("div[contenteditable]");
let beforeinput = [];
let input = [];
editor.addEventListener("beforeinput", (e) => {
// NOTE: Blink makes `getTargetRanges()` return empty range after propagaion,
// but this test wants to check the result during propagation.
// Therefore, we need to cache the result, but will assert if
// `getTargetRanges()` returns different ranges after checking the
// cached ranges.
e.cachedRanges = e.getTargetRanges();
beforeinput.push(e);
});
editor.addEventListener("input", (e) => {
e.cachedRanges = e.getTargetRanges();
input.push(e);
});
function reset() {
editor.focus();
beforeinput = [];
input = [];
}
function getRangeDescription(range) {
function getNodeDescription(node) {
if (!node) {
return "null";
}
switch (node.nodeType) {
case Node.TEXT_NODE:
case Node.COMMENT_NODE:
case Node.CDATA_SECTION_NODE:
return `${node.nodeName} "${node.data}"`;
case Node.ELEMENT_NODE:
return `<${node.nodeName.toLowerCase()}>`;
default:
return `${node.nodeName}`;
}
}
if (range === null) {
return "null";
}
if (range === undefined) {
return "undefined";
}
return range.startContainer == range.endContainer && range.startOffset == range.endOffset
? `(${getNodeDescription(range.startContainer)}, ${range.startOffset})`
: `(${getNodeDescription(range.startContainer)}, ${range.startOffset}) - (${getNodeDescription(range.endContainer)}, ${range.endOffset})`;
}
function getArrayOfRangesDescription(arrayOfRanges) {
if (arrayOfRanges === null) {
return "null";
}
if (arrayOfRanges === undefined) {
return "undefined";
}
if (!Array.isArray(arrayOfRanges)) {
return "Unknown Object";
}
if (arrayOfRanges.length === 0) {
return "[]";
}
let result = "[";
for (let range of arrayOfRanges) {
result += `{${getRangeDescription(range)}},`;
}
result += "]";
return result;
}
function sendBackspaceKey(modifier) {
if (!modifier) {
return new test_driver.Actions()
.keyDown(kBackspaceKey)
.keyUp(kBackspaceKey)
.send();
}
return new test_driver.Actions()
.keyDown(modifier)
.keyDown(kBackspaceKey)
.keyUp(kBackspaceKey)
.keyUp(modifier)
.send();
}
function checkGetTargetRangesKeepReturningSameValue(event) {
// https://github.com/w3c/input-events/issues/114
assert_equals(getArrayOfRangesDescription(event.getTargetRanges()),
getArrayOfRangesDescription(event.cachedRanges),
`${event.type}.getTargetRanges() should keep returning the same array of ranges even after its propagation finished`);
}
function checkGetTargetRangesOfBeforeinputOnDeleteSomething(expectedRange) {
assert_equals(beforeinput.length, 1,
"One beforeinput event should be fired if the key operation deletes something");
assert_true(Array.isArray(beforeinput[0].cachedRanges),
"beforeinput[0].getTargetRanges() should return an array of StaticRange instances during propagation");
// Before checking the length of array of ranges, we should check first range
// first because the first range data is more important than whether there
// are additional unexpected ranges.
if (beforeinput[0].cachedRanges.length > 0) {
assert_equals(
getRangeDescription(beforeinput[0].cachedRanges[0]),
getRangeDescription(expectedRange),
`beforeinput.getTargetRanges() should return expected range (inputType is "${beforeinput[0].inputType}")`);
assert_equals(beforeinput[0].cachedRanges.length, 1,
"beforeinput.getTargetRanges() should return one range within an array");
}
assert_equals(beforeinput[0].cachedRanges, 1,
"One range should be returned from getTargetRanges() when the key operation deletes something");
checkGetTargetRangesKeepReturningSameValue(beforeinput[0]);
}
function checkGetTargetRangesOfInputOnDeleteSomething() {
assert_equals(input.length, 1,
"One input event should be fired if the key operation deletes something");
// https://github.com/w3c/input-events/issues/113
assert_true(Array.isArray(input[0].cachedRanges),
"input[0].getTargetRanges() should return an array of StaticRange instances during propagation");
assert_equals(input[0].cachedRanges.length, 0,
"input[0].getTargetRanges() should return empty array during propagation");
checkGetTargetRangesKeepReturningSameValue(input[0]);
}
function checkBeforeinputAndInputEventsOnNOOP() {
assert_equals(beforeinput.length, 0,
"beforeinput event shouldn't be fired when the key operation does not cause modifying the DOM tree");
assert_equals(input.length, 0,
"input event shouldn't be fired when the key operation does not cause modifying the DOM tree");
}
// Simply deletes the previous ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 1);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>bc</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 0,
endContainer: editor.firstChild.firstChild,
endOffset: 1,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>a[]bc</p>"');
// Simply deletes the previous ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 2);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 1,
endContainer: editor.firstChild.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>ab[]c</p>"');
// Simply deletes the previous ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 3);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>ab</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 2,
endContainer: editor.firstChild.firstChild,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc[]</p>"');
// Should delete the `<span>` element becase it becomes empty.
// However, we need discussion whether the `<span>` element should be
// contained by a range of `getTargetRanges()`.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>a<span>b</span>c</p>";
let c = editor.querySelector("span").nextSibling;
selection.collapse(c, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: c,
endOffset: 0,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>a<span>b</span>[]c</p>"');
// Should delete the `<span>` element becase it becomes empty.
// However, we need discussion whether the `<span>` element should be
// contained by a range of `getTargetRanges()`.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>a<span>b</span>c</p>";
let b = editor.querySelector("span").firstChild;
selection.collapse(b, 1);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: editor.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>a<span>b[]</span>c</p>"');
// Invisible leading white-space may be deleted when the first visible
// character is deleted. If it's deleted, it should be contained by
// the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p> abc</p>";
selection.collapse(editor.firstChild.firstChild, 2);
await sendBackspaceKey();
assert_in_array(editor.innerHTML, ["<p>bc</p>", "<p> bc</p>"]);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: editor.firstChild.firstChild.length == 2 ? 0 : 1,
endContainer: editor.firstChild.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p> a[]bc</p>"');
// Invisible leading white-spaces in current block and invisible trailing
// white-spaces in the previous block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 3);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc </p><p> []def</p>"');
// Invisible leading white-spaces in current block and invisible trailing
// white-spaces in the previous block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 2);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc </p><p> [] def</p>"');
// Invisible leading white-spaces in current block and invisible trailing
// white-spaces in the previous block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 1);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc </p><p> [] def</p>"');
// Invisible leading white-spaces in current block and invisible trailing
// white-spaces in the previous block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc </p><p>[] def</p>"');
// Invisible leading white-spaces in current block and invisible trailing
// white-spaces in the previous block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.setBaseAndExtent(abc, 6, def, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc [</p><p>] def</p>"');
// Invisible leading white-spaces in the current block should be deleted
// for avoiding they becoming visible when the blocks are joined, but
// preformatted trailing white-spaces in the first block shouldn't be
// deleted. Perhaps, the invisible white-spaces should be contained by
// the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<pre>abc </pre><p> def</p>";
let pre = editor.firstChild;
let abc = pre.firstChild;
let p = pre.nextSibling;
let def = p.firstChild;
selection.collapse(def, 3);
await sendBackspaceKey();
// https://github.com/w3c/input-events/issues/112
// Shouldn't make the invisible white-spaces visible.
assert_equals(editor.innerHTML, "<pre>abc def</pre>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 6,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<pre>abc </pre><p> []def</p>"');
// Invisible leading/trailing white-spaces in the current block should be
// deleted for avoiding they becoming visible when the blocks are joined, but
// preformatted trailing white-spaces in the first block shouldn't be
// deleted. Perhaps, the invisible leading white-spaces should be contained
// by the range of `getTargetRanges()`, but needs discussion.
// And also not sure whether the trailing white-spaces should be contained
// by additional range of `getTargetRanges()` or not because of the
// implementation cost and runtime cost. Needs discuss.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<pre>abc </pre><p> def </p>";
let pre = editor.firstChild;
let abc = pre.firstChild;
let p = pre.nextSibling;
let def = p.firstChild;
selection.collapse(def, 3);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<pre>abc def </pre>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 6,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<pre>abc </pre><p> []def </p>"');
// Invisible trailing white-spaces in the first block should be deleted
// when the block is joined with the preformated following block, but
// the leading white-spaces in the preformatted block shouldn't be
// removed. So, in this case, the invisible trailing white-spaces should
// be in the range of `getTargetRanges()`, but not so for the preformatted
// visible leading white-spaces. But needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><pre> def</pre>";
let p = editor.firstChild;
let abc = p.firstChild;
let pre = p.nextSibling;
let def = pre.firstChild;
selection.collapse(def, 0);
await sendBackspaceKey();
assert_in_array(editor.innerHTML, ["<p>abc &nbsp; def</p>",
"<p>abc&nbsp;&nbsp; def</p>",
"<p>abc&nbsp; &nbsp;def</p>",
"<p>abc &nbsp;&nbsp;def</p>"]);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 6,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc </p><pre>[] def</pre>"');
// If the first block has invisible `<br>` element and joining it with
// the following block, the invisible trailing `<br>` element should be
// deleted and join the blocks. Therefore, the target range should contain
// the `<br>` element and block boundaries. But maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br></p><p>def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p1,
startOffset: 1,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc<br></p><p>[]def</p>"');
// If the first block has invisible `<br>` element for empty last line and
// joining it with the following block, the invisible trailing `<br>` element
// should be deleted and join the blocks. Therefore, the target range should
// contain the `<br>` element and block boundaries. But maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br><br></p><p>def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abc<br>def</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p1,
startOffset: 2,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc<br><br></p><p>[]def</p>"');
// Deleting visible `<br>` element should be contained by a range of
// `getTargetRanges()`.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br>def</p>";
let def = editor.querySelector("br").nextSibling;
selection.collapse(def, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc<br>[]def</p>"');
// Deleting visible `<br>` element should be contained by a range of
// `getTargetRanges()`. However, when only the `<br>` element is selected,
// the range shouldn't start from nor end by surrounding text nodes?
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br>def</p>";
selection.setBaseAndExtent(editor.firstChild, 1, editor.firstChild, 2);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: editor.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc{<br>}def</p>"');
// Joining parent block and child block should remove invisible preceding
// white-spaces of the child block and invisible leading white-spaces in
// the child block, and they should be contained in a range of
// `getTargetRanges()`, but maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<div>abc <p> def<br>ghi</p></div>";
let p = editor.querySelector("p");
let def = p.firstChild;
let abc = editor.firstChild.firstChild;
selection.collapse(def, 3);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<div>abcdef<p>ghi</p></div>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<div>abc <p> []def<br>ghi</p></div>"');
// Joining child block and parent block should remove invisible trailing
// white-spaces of the child block and invisible following white-spaces
// in the parent block, and they should be contained by a range of
// `getTaregetRanges()`, but maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<div><p>abc </p> def</div>";
let abc = editor.querySelector("p").firstChild;
let def = editor.querySelector("p").nextSibling;
selection.collapse(def, 3);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<div><p>abcdef</p></div>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<div><p>abc </p> []def</div>"');
// The following tests check whether the range returned from
// `beforeinput[0].getTargetRanges()` is modified or different range is
// modified instead. I.e., they don't test which type of deletion should
// occur. Therefore, their result depends on browser's key bindings,
// system settings and running OS.
function getFirstDifferentOffset(currentString, originalString) {
for (let i = 0; i < currentString.length; i++) {
if (currentString.charAt(i) !== originalString.charAt(i) &&
(originalString.charAt(i) !== " " || !currentString.charAt("\u00A0"))) {
return i;
}
}
return currentString.length;
}
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc def".length);
await sendBackspaceKey(kShift);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Shift + Backspace at "<p>abc def[] ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc def".length);
await sendBackspaceKey(kControl);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Control + Backspace at "<p>abc def[] ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc def".length);
await sendBackspaceKey(kAlt);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Alt + Backspace at "<p>abc def[] ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc def".length);
await sendBackspaceKey(kMeta);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Meta + Backspace at "<p>abc def[] ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p> ${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc".length);
await sendBackspaceKey(kShift);
let visibleText = p.firstChild.data.replace(/^\s+/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${invisibleWhiteSpaces + kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Shift + Backspace at "<p> abc[] def</p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p> ${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc".length);
await sendBackspaceKey(kControl);
let visibleText = p.firstChild.data.replace(/^\s+/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${invisibleWhiteSpaces + kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Control + Backspace at "<p> abc[] def</p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p> ${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc".length);
await sendBackspaceKey(kAlt);
let visibleText = p.firstChild.data.replace(/^\s+/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${invisibleWhiteSpaces + kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Alt + Backspace at "<p> abc[] def</p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p> ${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc".length);
await sendBackspaceKey(kMeta);
let visibleText = p.firstChild.data.replace(/^\s+/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${invisibleWhiteSpaces + kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Meta + Backspace at "<p> abc[] def</p>"');
</script>

Просмотреть файл

@ -0,0 +1,808 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>InputEvent.getTargetRanges() at Delete (forward delete)</title>
<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>
<div contenteditable></div>
<script>
const kDeleteKey = "\uE017";
const kShift = "\uE008";
const kMeta = "\uE03d";
const kControl = "\uE009";
const kAlt = "\uE00A";
let selection = getSelection();
let editor = document.querySelector("div[contenteditable]");
let beforeinput = [];
let input = [];
editor.addEventListener("beforeinput", (e) => {
// NOTE: Blink makes `getTargetRanges()` return empty range after propagaion,
// but this test wants to check the result during propagation.
// Therefore, we need to cache the result, but will assert if
// `getTargetRanges()` returns different ranges after checking the
// cached ranges.
e.cachedRanges = e.getTargetRanges();
beforeinput.push(e);
});
editor.addEventListener("input", (e) => {
e.cachedRanges = e.getTargetRanges();
input.push(e);
});
function reset() {
editor.focus();
beforeinput = [];
input = [];
}
function getRangeDescription(range) {
function getNodeDescription(node) {
if (!node) {
return "null";
}
switch (node.nodeType) {
case Node.TEXT_NODE:
case Node.COMMENT_NODE:
case Node.CDATA_SECTION_NODE:
return `${node.nodeName} "${node.data}"`;
case Node.ELEMENT_NODE:
return `<${node.nodeName.toLowerCase()}>`;
default:
return `${node.nodeName}`;
}
}
if (range === null) {
return "null";
}
if (range === undefined) {
return "undefined";
}
return range.startContainer == range.endContainer && range.startOffset == range.endOffset
? `(${getNodeDescription(range.startContainer)}, ${range.startOffset})`
: `(${getNodeDescription(range.startContainer)}, ${range.startOffset}) - (${getNodeDescription(range.endContainer)}, ${range.endOffset})`;
}
function getArrayOfRangesDescription(arrayOfRanges) {
if (arrayOfRanges === null) {
return "null";
}
if (arrayOfRanges === undefined) {
return "undefined";
}
if (!Array.isArray(arrayOfRanges)) {
return "Unknown Object";
}
if (arrayOfRanges.length === 0) {
return "[]";
}
let result = "[";
for (let range of arrayOfRanges) {
result += `{${getRangeDescription(range)}},`;
}
result += "]";
return result;
}
function sendDeleteKey(modifier) {
if (!modifier) {
return new test_driver.Actions()
.keyDown(kDeleteKey)
.keyUp(kDeleteKey)
.send();
}
return new test_driver.Actions()
.keyDown(modifier)
.keyDown(kDeleteKey)
.keyUp(kDeleteKey)
.keyUp(modifier)
.send();
}
function checkGetTargetRangesKeepReturningSameValue(event) {
// https://github.com/w3c/input-events/issues/114
assert_equals(getArrayOfRangesDescription(event.getTargetRanges()),
getArrayOfRangesDescription(event.cachedRanges),
`${event.type}.getTargetRanges() should keep returning the same array of ranges even after its propagation finished`);
}
function checkGetTargetRangesOfBeforeinputOnDeleteSomething(expectedRange) {
assert_equals(beforeinput.length, 1,
"One beforeinput event should be fired if the key operation deletes something");
assert_true(Array.isArray(beforeinput[0].cachedRanges),
"beforeinput[0].getTargetRanges() should return an array of StaticRange instances during propagation");
// Before checking the length of array of ranges, we should check the first
// range first because the first range data is more important than whether
// there are additional unexpected ranges.
if (beforeinput[0].cachedRanges.length > 0) {
assert_equals(
getRangeDescription(beforeinput[0].cachedRanges[0]),
getRangeDescription(expectedRange),
`beforeinput.getTargetRanges() should return expected range (inputType is "${beforeinput[0].inputType}")`);
assert_equals(beforeinput[0].cachedRanges.length, 1,
"beforeinput.getTargetRanges() should return one range within an array");
}
assert_equals(beforeinput[0].cachedRanges, 1,
"One range should be returned from getTargetRanges() when the key operation deletes something");
checkGetTargetRangesKeepReturningSameValue(beforeinput[0]);
}
function checkGetTargetRangesOfInputOnDeleteSomething() {
assert_equals(input.length, 1,
"One input event should be fired if the key operation deletes something");
// https://github.com/w3c/input-events/issues/113
assert_true(Array.isArray(input[0].cachedRanges),
"input[0].getTargetRanges() should return an array of StaticRange instances during propagation");
assert_equals(input[0].cachedRanges.length, 0,
"input[0].getTargetRanges() should return empty array during propagation");
checkGetTargetRangesKeepReturningSameValue(input[0]);
}
function checkBeforeinputAndInputEventsOnNOOP() {
assert_equals(beforeinput.length, 0,
"beforeinput event shouldn't be fired when the key operation does not cause modifying the DOM tree");
assert_equals(input.length, 0,
"input event shouldn't be fired when the key operation does not cause modifying the DOM tree");
}
// Simply deletes the next ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 2);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>ab</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 2,
endContainer: editor.firstChild.firstChild,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>ab[]c</p>"');
// Simply deletes the next ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 1);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 1,
endContainer: editor.firstChild.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>a[]bc</p>"');
// Simply deletes the next ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 0);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>bc</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 0,
endContainer: editor.firstChild.firstChild,
endOffset: 1,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>[]abc</p>"');
// Should delete the `<span>` element becase it becomes empty.
// However, we need discussion whether the `<span>` element should be
// contained by a range of `getTargetRanges()`.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>a<span>b</span>c</p>";
let a = editor.querySelector("span").previousSibling;
selection.collapse(a, 1);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: a,
startOffset: 1,
endContainer: editor.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>a[]<span>b</span>c</p>"');
// Should delete the `<span>` element becase it becomes empty.
// However, we need discussion whether the `<span>` element should be
// contained by a range of `getTargetRanges()`.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>a<span>b</span>c</p>";
let b = editor.querySelector("span").firstChild;
selection.collapse(b, 0);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: editor.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>a<span>[]b</span>c</p>"');
// Invisible trailing white-space may be deleted when the last visible
// character is deleted. If it's deleted, it should be contained by
// the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p>";
selection.collapse(editor.firstChild.firstChild, 2);
await sendDeleteKey();
assert_in_array(editor.innerHTML, ["<p>ab</p>", "<p>ab </p>"]);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 2,
endContainer: editor.firstChild.firstChild,
endOffset: editor.firstChild.firstChild.data.length == 2 ? 4 : 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>ab[]c </p>"');
// Invisible trailing white-spaces in current block and invisible leading
// white-spaces in the following block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc[] </p><p> def</p>"');
// Invisible trailing white-spaces in current block and invisible leading
// white-spaces in the following block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(abc, 4);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc [] </p><p> def</p>"');
// Invisible trailing white-spaces in current block and invisible leading
// white-spaces in the following block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(abc, 5);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc [] </p><p> def</p>"');
// Invisible trailing white-spaces in current block and invisible leading
// white-spaces in the following block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(abc, 6);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc []</p><p> def</p>"');
// Invisible trailing white-spaces in current block and invisible leading
// white-spaces in the following block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.setBaseAndExtent(abc, 6, def, 0);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc [</p><p>] def</p>"');
// Invisible leading white-spaces in the following block should be deleted
// for avoiding they becoming visible when the blocks are joined, but
// preformatted trailing white-spaces in the first block shouldn't be
// deleted. Perhaps, the invisible white-spaces should be contained by
// the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<pre>abc </pre><p> def</p>";
let pre = editor.firstChild;
let abc = pre.firstChild;
let p = pre.nextSibling;
let def = p.firstChild;
selection.collapse(abc, 6);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<pre>abc def</pre>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 6,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<pre>abc []</pre><p> def</p>"');
// Invisible leading/trailing white-spaces in the following block should be
// deleted for avoiding they becoming visible when the blocks are joined, but
// preformatted trailing white-spaces in the first block shouldn't be
// deleted. Perhaps, the invisible leading white-spaces should be contained
// by the range of `getTargetRanges()`, but needs discussion.
// And also not sure whether the trailing white-spaces should be contained
// by additional range of `getTargetRanges()` or not because of the
// implementation cost and runtime cost. Needs discuss.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<pre>abc </pre><p> def</p>";
let pre = editor.firstChild;
let abc = pre.firstChild;
let p = pre.nextSibling;
let def = p.firstChild;
selection.collapse(abc, 6);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<pre>abc def</pre>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 6,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<pre>abc []</pre><p> def </p>"');
// Invisible trailing white-spaces in the first block should be deleted
// when the block is joined with the preformated following block, but
// the leading white-spaces in the preformatted block shouldn't be
// removed. So, in this case, the invisible trailing white-spaces should
// be in the range of `getTargetRanges()`, but not so for the preformatted
// visible leading white-spaces. But needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><pre> def</pre>";
let p = editor.firstChild;
let abc = p.firstChild;
let pre = p.nextSibling;
let def = pre.firstChild;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_in_array(editor.innerHTML, ["<p>abc &nbsp; def</p>",
"<p>abc&nbsp;&nbsp; def</p>",
"<p>abc&nbsp; &nbsp;def</p>",
"<p>abc &nbsp;&nbsp;def</p>"]);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc[] </p><pre> def</pre>"');
// Deleting from before invisible trailing `<br>` element of a block
// should delete the `<br>` element and join the blocks. Therefore,
// the target range should contain the `<br>` element and block boundaries.
// But maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br></p><p>def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc[]<br></p><p>def</p>"');
// Deleting from last empty line in the first block should delete the
// invisible `<br>` element for the last empty line and join the blocks.
// In this case, the invisible `<br>` element should be contained in the
// range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br><br></p><p>def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(p1, 2);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abc<br>def</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p1,
startOffset: 2,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc<br>{}<br></p><p>def</p>"');
// Deleting visible `<br>` element should be contained by a range of
// `getTargetRanges()`.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br>def</p>";
let abc = editor.firstChild.firstChild;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: editor.querySelector("p"),
endOffset: 2,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc[]<br>def</p>"');
// Deleting visible `<br>` element should be contained by a range of
// `getTargetRanges()`. However, when only the `<br>` element is selected,
// the range shouldn't start from nor end by surrounding text nodes?
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br>def</p>";
selection.setBaseAndExtent(editor.firstChild, 1, editor.firstChild, 2);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: editor.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc{<br>}def</p>"');
// Joining parent block and child block should remove invisible preceding
// white-spaces of the child block and invisible leading white-spaces in
// the child block, and they should be contained in a range of
// `getTargetRanges()`, but maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<div>abc <p> def<br>ghi</p></div>";
let p = editor.querySelector("p");
let def = p.firstChild;
let abc = editor.firstChild.firstChild;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<div>abcdef<p>ghi</p></div>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<div>abc[] <p> def<br>ghi</p></div>"');
// Joining child block and parent block should remove invisible trailing
// white-spaces of the child block and invisible following white-spaces
// in the parent block, and they should be contained by a range of
// `getTaregetRanges()`, but maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<div><p>abc </p> def</div>";
let abc = editor.querySelector("p").firstChild;
let def = editor.querySelector("p").nextSibling;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<div><p>abcdef</p></div>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<div><p>abc[] </p> def</div>"');
// The following tests check whether the range returned from
// `beforeinput[0].getTargetRanges()` is modified or different range is
// modified instead. I.e., they don't test which type of deletion should
// occur. Therefore, their result depends on browser's key bindings,
// system settings and running OS.
function getFirstDifferentOffset(currentString, originalString) {
for (let i = 0; i < currentString.length; i++) {
if (currentString.charAt(i) !== originalString.charAt(i) &&
(originalString.charAt(i) !== " " || !currentString.charAt("\u00A0"))) {
return i;
}
}
return currentString.length;
}
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kShift);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Shift + Delete at "<p>abc []def ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kControl);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Control + Delete at "<p>abc []def ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kAlt);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Alt + Delete at "<p>abc []def ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kMeta);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Meta + Delete at "<p>abc []def ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p>${kText} </p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kShift);
let visibleText = p.firstChild.data.replace(/%s+$/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length) + invisibleWhiteSpaces}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Shift + Delete at "<p>abc []def </p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p>${kText} </p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kControl);
let visibleText = p.firstChild.data.replace(/%s+$/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length) + invisibleWhiteSpaces}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Control + Delete at "<p>abc []def </p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p>${kText} </p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kAlt);
let visibleText = p.firstChild.data.replace(/%s+$/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length) + invisibleWhiteSpaces}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Alt + Delete at "<p>abc []def </p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p>${kText} s</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kMeta);
let visibleText = p.firstChild.data.replace(/%s+$/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length) + invisibleWhiteSpaces}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Meta + Delete at "<p>abc []def</p>"');
</script>