292 lines
12 KiB
HTML
292 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>EditContext: The HTMLElement.editContext property</title>
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="/resources/testdriver.js"></script>
|
|
<script src="/resources/testdriver-actions.js"></script>
|
|
<script src="/resources/testdriver-vendor.js"></script>
|
|
</head>
|
|
<body>
|
|
<script>
|
|
const kBackspaceKey = "\uE003";
|
|
const kDeleteKey = "\uE017";
|
|
|
|
async function testBasicTestInput(element) {
|
|
const editContext = new EditContext();
|
|
let textForView = "";
|
|
document.body.appendChild(element);
|
|
let beforeInputType = null;
|
|
let beforeInputTargetRanges = null;
|
|
element.addEventListener("beforeinput", e => {
|
|
beforeInputType = e.inputType;
|
|
beforeInputTargetRanges = e.getTargetRanges().map(
|
|
staticRange => [staticRange.startOffset, staticRange.endOffset]);
|
|
});
|
|
editContext.addEventListener("textupdate", e => {
|
|
textForView = `${textForView.substring(0, e.updateRangeStart)}${e.text}${textForView.substring(e.updateRangeEnd)}`;
|
|
});
|
|
element.editContext = editContext;
|
|
element.focus();
|
|
await test_driver.send_keys(element, 'a');
|
|
assert_equals(editContext.text, "a");
|
|
assert_equals(textForView, "a");
|
|
assert_equals(beforeInputType, "insertText");
|
|
if (element instanceof HTMLCanvasElement) {
|
|
// DOM selection doesn't work inside <canvas>, so events
|
|
// in <canvas> can't have target ranges.
|
|
assert_equals(beforeInputTargetRanges.length, 0);
|
|
} else {
|
|
assert_equals(beforeInputTargetRanges.length, 1);
|
|
assert_array_equals(beforeInputTargetRanges[0], [0, 0]);
|
|
}
|
|
|
|
element.remove();
|
|
}
|
|
|
|
promise_test(testBasicTestInput.bind(null, document.createElement("div")), "Basic text input with div");
|
|
promise_test(testBasicTestInput.bind(null, document.createElement("canvas")), "Basic text input with canvas");
|
|
|
|
async function testBasicTestInputWithExistingSelection(element) {
|
|
const editContext = new EditContext();
|
|
let textForView = "";
|
|
document.body.appendChild(element);
|
|
editContext.addEventListener("textupdate", e => {
|
|
textForView = `${textForView.substring(0, e.updateRangeStart)}${e.text}${textForView.substring(e.updateRangeEnd)}`;
|
|
});
|
|
element.editContext = editContext;
|
|
element.focus();
|
|
|
|
editContext.updateText(0, 0, "abcd");
|
|
textForView = "abcd";
|
|
assert_equals(editContext.text, "abcd");
|
|
editContext.updateSelection(2, 3);
|
|
await test_driver.send_keys(element, 'Z');
|
|
assert_equals(editContext.text, "abZd");
|
|
assert_equals(textForView, "abZd");
|
|
|
|
editContext.updateSelection(2, 1);
|
|
await test_driver.send_keys(element, 'Y');
|
|
assert_equals(editContext.text, "aYZd");
|
|
assert_equals(textForView, "aYZd");
|
|
|
|
element.remove();
|
|
}
|
|
|
|
promise_test(testBasicTestInputWithExistingSelection.bind(null, document.createElement("div")), "Text insertion with non-collapsed selection with div");
|
|
promise_test(testBasicTestInputWithExistingSelection.bind(null, document.createElement("canvas")), "Text insertion with non-collapsed selection with canvas");
|
|
|
|
promise_test(async function() {
|
|
const editContext = new EditContext();
|
|
assert_not_equals(editContext, null);
|
|
const test = document.createElement("div");
|
|
document.body.appendChild(test);
|
|
test.editContext = editContext;
|
|
test.focus();
|
|
await test_driver.send_keys(test, 'a');
|
|
assert_equals(test.innerHTML, "");
|
|
test.remove();
|
|
}, 'EditContext should disable DOM mutation');
|
|
|
|
promise_test(async function() {
|
|
const editContext = new EditContext();
|
|
assert_not_equals(editContext, null);
|
|
const test = document.createElement("div");
|
|
document.body.appendChild(test);
|
|
test.focus();
|
|
test.editContext = editContext;
|
|
test.addEventListener("beforeinput", e => {
|
|
if (e.inputType === "insertText") {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
await test_driver.send_keys(test, 'a');
|
|
assert_equals(editContext.text, "");
|
|
test.remove();
|
|
}, 'beforeInput(insertText) should be cancelable');
|
|
|
|
promise_test(async () => {
|
|
let div = document.createElement("div");
|
|
document.body.appendChild(div);
|
|
let divText = "Hello World";
|
|
div.innerText = divText;
|
|
div.editContext = new EditContext();
|
|
div.focus();
|
|
let got_before_input_event = false;
|
|
div.addEventListener("beforeinput", e => {
|
|
got_before_input_event = true;
|
|
});
|
|
let got_textupdate_event = false;
|
|
div.editContext.addEventListener("textupdate", e => {
|
|
got_textupdate_event = true;
|
|
});
|
|
|
|
div.editContext = null;
|
|
await test_driver.send_keys(div, "a");
|
|
|
|
assert_false(got_textupdate_event, "Shouldn't have received textupdate event after editContext was detached");
|
|
assert_false(got_before_input_event, "Shouldn't have received beforeinput event after editContext was detached");
|
|
|
|
div.remove();
|
|
}, "EditContext should not receive events after being detached from element");
|
|
|
|
async function testBackspaceAndDelete(element) {
|
|
const editContext = new EditContext();
|
|
let textForView = "hello there";
|
|
document.body.appendChild(element);
|
|
let beforeInputType = null;
|
|
let beforeInputTargetRanges = null;
|
|
element.addEventListener("beforeinput", e => {
|
|
beforeInputType = e.inputType;
|
|
beforeInputTargetRanges = e.getTargetRanges().map(
|
|
staticRange => [staticRange.startOffset, staticRange.endOffset]);
|
|
});
|
|
let textUpdateSelection = null;
|
|
editContext.addEventListener("textupdate", e => {
|
|
textUpdateSelection = [e.selectionStart, e.selectionEnd];
|
|
textForView = `${textForView.substring(0, e.updateRangeStart)}${e.text}${textForView.substring(e.updateRangeEnd)}`;
|
|
});
|
|
element.editContext = editContext;
|
|
editContext.updateText(0, 11, "hello there");
|
|
editContext.updateSelection(10, 10);
|
|
const selection = window.getSelection();
|
|
|
|
await test_driver.send_keys(element, kBackspaceKey);
|
|
assert_equals(textForView, "hello thee");
|
|
assert_array_equals(textUpdateSelection, [9, 9]);
|
|
assert_equals(beforeInputType, "deleteContentBackward");
|
|
assert_equals(beforeInputTargetRanges.length, 0, "Backspace should not have a target range in EditContext");
|
|
|
|
await test_driver.send_keys(element, kDeleteKey);
|
|
assert_equals(textForView, "hello the");
|
|
assert_array_equals(textUpdateSelection, [9, 9]);
|
|
assert_equals(beforeInputType, "deleteContentForward");
|
|
assert_equals(beforeInputTargetRanges.length, 0, "Delete should not have a target range in EditContext");
|
|
element.remove();
|
|
}
|
|
|
|
promise_test(testBackspaceAndDelete.bind(null, document.createElement("div")), "Backspace and delete in EditContext with div");
|
|
promise_test(testBackspaceAndDelete.bind(null, document.createElement("canvas")) , "Backspace and delete in EditContext with canvas");
|
|
|
|
async function testBackspaceAndDeleteWithExistingSelection(element) {
|
|
const editContext = new EditContext();
|
|
let textForView = "hello there";
|
|
document.body.appendChild(element);
|
|
let beforeInputType = null;
|
|
let beforeInputTargetRanges = null;
|
|
element.addEventListener("beforeinput", e => {
|
|
beforeInputType = e.inputType;
|
|
beforeInputTargetRanges = e.getTargetRanges().map(
|
|
staticRange => [staticRange.startOffset, staticRange.endOffset]);
|
|
});
|
|
let textUpdateSelection = null;
|
|
editContext.addEventListener("textupdate", e => {
|
|
textUpdateSelection = [e.selectionStart, e.selectionEnd];
|
|
textForView = `${textForView.substring(0, e.updateRangeStart)}${e.text}${textForView.substring(e.updateRangeEnd)}`;
|
|
});
|
|
element.editContext = editContext;
|
|
const initialText = "abcdefghijklmnopqrstuvwxyz";
|
|
editContext.updateText(0, initialText.length, initialText);
|
|
textForView = initialText;
|
|
element.focus();
|
|
|
|
editContext.updateSelection(3, 6);
|
|
await test_driver.send_keys(element, kBackspaceKey);
|
|
assert_equals(editContext.text, "abcghijklmnopqrstuvwxyz");
|
|
assert_equals(textForView, "abcghijklmnopqrstuvwxyz");
|
|
assert_array_equals(textUpdateSelection, [3, 3]);
|
|
assert_equals(beforeInputType, "deleteContentBackward");
|
|
assert_equals(beforeInputTargetRanges.length, 0, "Backspace should not have a target range in EditContext");
|
|
|
|
editContext.updateSelection(3, 6);
|
|
await test_driver.send_keys(element, kDeleteKey);
|
|
assert_equals(editContext.text, "abcjklmnopqrstuvwxyz");
|
|
assert_equals(textForView, "abcjklmnopqrstuvwxyz");
|
|
assert_array_equals(textUpdateSelection, [3, 3]);
|
|
assert_equals(beforeInputType, "deleteContentForward");
|
|
assert_equals(beforeInputTargetRanges.length, 0, "Delete should not have a target range in EditContext");
|
|
|
|
editContext.updateSelection(6, 3);
|
|
await test_driver.send_keys(element, kBackspaceKey);
|
|
assert_equals(editContext.text, "abcmnopqrstuvwxyz");
|
|
assert_equals(textForView, "abcmnopqrstuvwxyz");
|
|
assert_array_equals(textUpdateSelection, [3, 3]);
|
|
assert_equals(beforeInputType, "deleteContentBackward");
|
|
assert_equals(beforeInputTargetRanges.length, 0, "Backspace should not have a target range in EditContext");
|
|
|
|
editContext.updateSelection(6, 3);
|
|
await test_driver.send_keys(element, kDeleteKey);
|
|
assert_equals(editContext.text, "abcpqrstuvwxyz");
|
|
assert_equals(textForView, "abcpqrstuvwxyz");
|
|
assert_array_equals(textUpdateSelection, [3, 3]);
|
|
assert_equals(beforeInputType, "deleteContentForward");
|
|
assert_equals(beforeInputTargetRanges.length, 0, "Delete should not have a target range in EditContext");
|
|
|
|
element.remove();
|
|
}
|
|
|
|
promise_test(testBackspaceAndDeleteWithExistingSelection.bind(null, document.createElement("div")), "Backspace and delete with existing selection with div");
|
|
promise_test(testBackspaceAndDeleteWithExistingSelection.bind(null, document.createElement("canvas")) , "Backspace and delete with existing selection with canvas");
|
|
|
|
promise_test(async function() {
|
|
const iframe = document.createElement("iframe");
|
|
document.body.appendChild(iframe);
|
|
const editContext = new EditContext();
|
|
iframe.contentDocument.body.editContext = editContext;
|
|
iframe.contentDocument.body.focus();
|
|
let got_textupdate_event = false;
|
|
editContext.addEventListener("textupdate", e => {
|
|
got_textupdate_event = true;
|
|
});
|
|
await test_driver.send_keys(iframe.contentDocument.body, "a");
|
|
assert_equals(iframe.contentDocument.body.innerHTML, "", "EditContext should disable DOM modification in iframe.");
|
|
assert_true(got_textupdate_event, "Input in iframe EditContext should trigger textupdate event");
|
|
iframe.remove();
|
|
}, 'EditContext constructed outside iframe can be used in iframe');
|
|
|
|
promise_test(async function () {
|
|
const div = document.createElement("div");
|
|
document.body.appendChild(div);
|
|
const editContext = new EditContext();
|
|
div.editContext = editContext;
|
|
let textupdateEventCount = 0;
|
|
editContext.addEventListener("textupdate", e => {
|
|
textupdateEventCount++;
|
|
});
|
|
|
|
div.focus();
|
|
await test_driver.send_keys(div, 'a');
|
|
assert_equals(textupdateEventCount, 1);
|
|
assert_equals(div.innerHTML, '');
|
|
|
|
const iframe = document.createElement('iframe');
|
|
document.body.appendChild(iframe);
|
|
iframe.contentDocument.body.appendChild(div);
|
|
|
|
div.focus();
|
|
await test_driver.send_keys(div, 'b');
|
|
assert_equals(textupdateEventCount, 2);
|
|
assert_equals(div.innerHTML, '');
|
|
|
|
iframe.remove();
|
|
}, 'Textupdate event should be fired on edit context when the editor element is moved to an iframe');
|
|
|
|
promise_test(async function() {
|
|
const div = document.createElement("div");
|
|
const input = document.createElement("input");
|
|
document.body.appendChild(div);
|
|
document.body.appendChild(input);
|
|
const editContext = new EditContext();
|
|
div.editContext = editContext;
|
|
div.focus();
|
|
div.remove();
|
|
input.focus();
|
|
await test_driver.send_keys(input, "a");
|
|
assert_equals(input.value, "a", "input should have received text input");
|
|
|
|
input.remove();
|
|
}, 'Removing EditContext-associated element with focus doesn\'t prevent further text input on the page');
|
|
</script>
|
|
</body>
|
|
</html>
|