diff --git a/uievents/textInput/api.html b/uievents/textInput/api.html
new file mode 100644
index 00000000000000..a88184e500178c
--- /dev/null
+++ b/uievents/textInput/api.html
@@ -0,0 +1,88 @@
+
+
+
textInput: API
+
+
+
+
+
+
+
diff --git a/uievents/textInput/backspace.html b/uievents/textInput/backspace.html
new file mode 100644
index 00000000000000..c9b2f8bee4e1e4
--- /dev/null
+++ b/uievents/textInput/backspace.html
@@ -0,0 +1,16 @@
+
+
+textInput: backspace
+
+
+
+
+
+Press Backspace in each text field below.
+
+
+abc
+
+
diff --git a/uievents/textInput/basic.html b/uievents/textInput/basic.html
new file mode 100644
index 00000000000000..6335167faa84f5
--- /dev/null
+++ b/uievents/textInput/basic.html
@@ -0,0 +1,16 @@
+
+
+textInput: basic
+
+
+
+
+
+Type "a" into each text field below.
+
+
+
+
+
diff --git a/uievents/textInput/delete-selection.html b/uievents/textInput/delete-selection.html
new file mode 100644
index 00000000000000..f77e8c6e98a1ec
--- /dev/null
+++ b/uievents/textInput/delete-selection.html
@@ -0,0 +1,16 @@
+
+
+textInput: delete selection
+
+
+
+
+
+Press Delete (Fn+Backspace for macOS) in each text field below.
+
+
+abc
+
+
diff --git a/uievents/textInput/delete.html b/uievents/textInput/delete.html
new file mode 100644
index 00000000000000..2d696fba71a8f7
--- /dev/null
+++ b/uievents/textInput/delete.html
@@ -0,0 +1,16 @@
+
+
+textInput: delete
+
+
+
+
+
+Press Delete (Fn+Backspace for macOS) in each text field below.
+
+
+abc
+
+
diff --git a/uievents/textInput/enter-input.html b/uievents/textInput/enter-input.html
new file mode 100644
index 00000000000000..032503694dc8f8
--- /dev/null
+++ b/uievents/textInput/enter-input.html
@@ -0,0 +1,14 @@
+
+
+textInput: Enter key for input element
+
+
+
+
+
+Press Enter in the text field below.
+
+
+
diff --git a/uievents/textInput/enter-textarea-contenteditable.html b/uievents/textInput/enter-textarea-contenteditable.html
new file mode 100644
index 00000000000000..60891df982e9f6
--- /dev/null
+++ b/uievents/textInput/enter-textarea-contenteditable.html
@@ -0,0 +1,15 @@
+
+
+textInput: Enter key for textarea and contenteditable
+
+
+
+
+
+Press Enter in each text field below.
+
+
+
+
diff --git a/uievents/textInput/smiley-manual.html b/uievents/textInput/smiley-manual.html
new file mode 100644
index 00000000000000..7781f6e1b06b56
--- /dev/null
+++ b/uievents/textInput/smiley-manual.html
@@ -0,0 +1,17 @@
+
+
+textInput: smiley (manual)
+
+
+
+Type "🙂" into each text field below.
+
+
+
+
+
+
diff --git a/uievents/textInput/support/basic.sub.js b/uievents/textInput/support/basic.sub.js
new file mode 100644
index 00000000000000..53ff826743a317
--- /dev/null
+++ b/uievents/textInput/support/basic.sub.js
@@ -0,0 +1,44 @@
+const els = document.querySelectorAll('.test-el');
+const key = "{{GET[key]}}";
+const keyRaw = keyMapping[key] || key;
+const expectedData = key === "Enter" ? "\n" : key;
+const selectionStart = {{GET[selectionStart]}};
+const selectionEnd = {{GET[selectionEnd]}};
+const expectedValue = "{{GET[expectedValue]}}";
+
+for (const el of els) {
+ promise_test(t => {
+ return new Promise((resolve, reject) => {
+ let textInputEvents = 0;
+ el.addEventListener('textInput', t.step_func(e => {
+ textInputEvents++;
+ assert_equals(e.data, expectedData);
+ assert_true(e.bubbles);
+ assert_true(e.cancelable);
+ assert_equals(e.view, window);
+ assert_equals(e.detail, 0);
+ assert_true(e instanceof window.TextEvent);
+ }));
+ el.addEventListener('input', t.step_func(e => {
+ assert_equals(textInputEvents, 1);
+ if (expectedValue === "\n" && !(el instanceof HTMLInputElement) && !(el instanceof HTMLTextAreaElement)) {
+ // New paragraph in contenteditable during editing is weird.
+ // innerHTML is
+ // ...but later changes to
+ // So, check that there's at least one
.
+ assert_true(getValue(el).indexOf('
') > -1);
+ } else {
+ assert_equals(getValue(el), expectedValue);
+ }
+ resolve();
+ }));
+ el.onfocus = t.step_func(e => {
+ if (window.test_driver) {
+ test_driver.send_keys(el, keyRaw);
+ }
+ });
+ el.focus();
+ setSelection(el, selectionStart, selectionEnd);
+ });
+ }, `${document.title}, ${elDesc(el)}`);
+}
diff --git a/uievents/textInput/support/common.js b/uievents/textInput/support/common.js
new file mode 100644
index 00000000000000..05600d464c879e
--- /dev/null
+++ b/uievents/textInput/support/common.js
@@ -0,0 +1,38 @@
+function elDesc(el) {
+ let rv = `<${el.localName}`;
+ if (el.hasAttribute('contenteditable')) {
+ rv += ` contenteditable="${el.getAttribute('contenteditable')}"`;
+ }
+ if (el.hasAttribute('type')) {
+ rv += ` type="${el.getAttribute('type')}"`;
+ }
+ rv += `>`;
+ return rv;
+}
+
+function setSelection(el) {
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
+ el.selectionStart = selectionStart;
+ el.selectionEnd = selectionEnd;
+ } else {
+ const s = getSelection();
+ s.removeAllRanges();
+ const r = new Range();
+ r.setStart(el.firstChild || el, selectionStart);
+ r.setEnd(el.firstChild || el, selectionEnd);
+ s.addRange(r);
+ }
+}
+
+function getValue(el) {
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
+ return el.value;
+ }
+ return el.innerHTML;
+}
+
+const keyMapping = {
+ "Enter": "\uE006",
+ "Backspace": "\uE003",
+ "Delete": "\uE017",
+};
diff --git a/uievents/textInput/support/no-textInput.sub.js b/uievents/textInput/support/no-textInput.sub.js
new file mode 100644
index 00000000000000..332cbf08f2b3c8
--- /dev/null
+++ b/uievents/textInput/support/no-textInput.sub.js
@@ -0,0 +1,29 @@
+const els = document.querySelectorAll('.test-el');
+const key = "{{GET[key]}}";
+const keyRaw = keyMapping[key] || key;
+const expectedData = key === "Enter" ? "\n" : key;
+const selectionStart = {{GET[selectionStart]}};
+const selectionEnd = {{GET[selectionEnd]}};
+const expectedValue = "{{GET[expectedValue]}}";
+
+for (const el of els) {
+ promise_test(t => {
+ return new Promise((resolve, reject) => {
+ el.addEventListener('textInput', reject);
+ el.addEventListener('keyup', t.step_func(e => {
+ if (e.key !== key) {
+ return;
+ }
+ assert_equals(getValue(el), expectedValue);
+ resolve();
+ }));
+ el.onfocus = t.step_func(e => {
+ if (window.test_driver) {
+ test_driver.send_keys(el, keyRaw);
+ }
+ });
+ el.focus();
+ setSelection(el, selectionStart, selectionEnd);
+ });
+ }, `${document.title}, ${elDesc(el)}`);
+}