LibWeb: Implement autocorrect attribute

This commit is contained in:
Callum Law 2025-05-26 17:36:15 +12:00 committed by Luke Wilde
commit 829437c11d
Notes: github-actions[bot] 2025-08-29 14:48:21 +00:00
6 changed files with 428 additions and 1 deletions

View file

@ -28,6 +28,7 @@ namespace AttributeNames {
__ENUMERATE_HTML_ATTRIBUTE(async, "async") \
__ENUMERATE_HTML_ATTRIBUTE(autocapitalize, "autocapitalize") \
__ENUMERATE_HTML_ATTRIBUTE(autocomplete, "autocomplete") \
__ENUMERATE_HTML_ATTRIBUTE(autocorrect, "autocorrect") \
__ENUMERATE_HTML_ATTRIBUTE(autofocus, "autofocus") \
__ENUMERATE_HTML_ATTRIBUTE(autoplay, "autoplay") \
__ENUMERATE_HTML_ATTRIBUTE(axis, "axis") \

View file

@ -31,6 +31,7 @@
#include <LibWeb/HTML/HTMLBodyElement.h>
#include <LibWeb/HTML/HTMLDialogElement.h>
#include <LibWeb/HTML/HTMLElement.h>
#include <LibWeb/HTML/HTMLInputElement.h>
#include <LibWeb/HTML/HTMLLabelElement.h>
#include <LibWeb/HTML/HTMLObjectElement.h>
#include <LibWeb/HTML/HTMLParagraphElement.h>
@ -2325,4 +2326,64 @@ void HTMLElement::set_autocapitalize(String const& given_value)
MUST(set_attribute(HTML::AttributeNames::autocapitalize, given_value));
}
// https://html.spec.whatwg.org/multipage/interaction.html#used-autocorrection-state
HTMLElement::AutocorrectionState HTMLElement::used_autocorrection_state() const
{
// The autocorrect attribute is an enumerated attribute with the following keywords and states:
// Keyword | State | Brief description
// on | on | The user agent is permitted to automatically correct spelling errors while the user
// (the empty string) | | types. Whether spelling is automatically corrected while typing left is for the user
// | | agent to decide, and may depend on the element as well as the user's preferences.
// off | off | The user agent is not allowed to automatically correct spelling while the user types.
// The attribute's invalid value default and missing value default are both the on state.
auto autocorrect_attribute_state = [](Optional<String> attribute) {
if (attribute.has_value() && attribute.value().equals_ignoring_ascii_case("off"sv))
return AutocorrectionState::Off;
return AutocorrectionState::On;
};
// To compute the used autocorrection state of an element element, run these steps:
// 1. If element is an input element whose type attribute is in one of the URL, E-mail, or Password states, then return off.
if (auto const* input_element = as_if<HTMLInputElement>(this)) {
if (first_is_one_of(input_element->type_state(), HTMLInputElement::TypeAttributeState::URL, HTMLInputElement::TypeAttributeState::Email, HTMLInputElement::TypeAttributeState::Password))
return AutocorrectionState::Off;
}
// 2. If the autocorrect content attribute is present on element, then return the state of the attribute.
auto maybe_autocorrect_attribute = attribute(HTML::AttributeNames::autocorrect);
if (maybe_autocorrect_attribute.has_value())
return autocorrect_attribute_state(maybe_autocorrect_attribute);
// 3. If element is an autocapitalize-and-autocorrect inheriting element and has a non-null form owner, then return
// the state of element's form owner's autocorrect attribute.
if (auto const* form_associated_element = as_if<FormAssociatedElement>(this)) {
if (form_associated_element->is_autocapitalize_and_autocorrect_inheriting() && form_associated_element->form())
return autocorrect_attribute_state(form_associated_element->form()->attribute(HTML::AttributeNames::autocorrect));
}
// 4. Return on.
return AutocorrectionState::On;
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-autocorrect
bool HTMLElement::autocorrect() const
{
// The autocorrect getter steps are: return true if the element's used autocorrection state is on and false if the element's used autocorrection state is off.
return used_autocorrection_state() == AutocorrectionState::On;
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-autocorrect
void HTMLElement::set_autocorrect(bool given_value)
{
// The setter steps are: if the given value is true, then the element's autocorrect attribute must be set to "on"; otherwise it must be set to "off".
if (given_value)
MUST(set_attribute(HTML::AttributeNames::autocorrect, "on"_string));
else
MUST(set_attribute(HTML::AttributeNames::autocorrect, "off"_string));
}
}

View file

@ -135,6 +135,15 @@ public:
String autocapitalize() const;
void set_autocapitalize(String const&);
enum class AutocorrectionState {
On,
Off
};
AutocorrectionState used_autocorrection_state() const;
bool autocorrect() const;
void set_autocorrect(bool);
bool fire_a_synthetic_pointer_event(FlyString const& type, DOM::Element& target, bool not_trusted);
// https://html.spec.whatwg.org/multipage/forms.html#category-label

View file

@ -27,7 +27,7 @@ interface HTMLElement : Element {
[CEReactions] attribute boolean spellcheck;
[CEReactions] attribute DOMString writingSuggestions;
[CEReactions] attribute DOMString autocapitalize;
[FIXME, CEReactions] attribute boolean autocorrect;
[CEReactions] attribute boolean autocorrect;
[LegacyNullToEmptyString, CEReactions] attribute Utf16DOMString innerText;
[LegacyNullToEmptyString, CEReactions] attribute Utf16DOMString outerText;

View file

@ -0,0 +1,14 @@
Harness status: OK
Found 9 tests
9 Pass
Pass Test that the autocorrect attribute is available on HTMLInputElement.
Pass Test that the autocorrect attribute is available on HTMLTextAreaElement.
Pass Test that the autocorrect attribute is available on div.
Pass Test that the autocorrect attribute is available on form.
Pass Test setting the autocorrect IDL attribute.
Pass Test setting the autocorrect attribute using setAttribute.
Pass Test inheriting autocorrection from a form.
Pass Test autocorrection in an editing host.
Pass Test autocorrection in password, URL, and email inputs.

View file

@ -0,0 +1,342 @@
<!DOCTYPE html>
<html>
<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#autocorrection">
<body>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script>
test(() => {
assert_true('autocorrect' in document.createElement('input'));
}, "Test that the autocorrect attribute is available on HTMLInputElement.");
test(() => {
assert_true('autocorrect' in document.createElement('textarea'));
}, "Test that the autocorrect attribute is available on HTMLTextAreaElement.");
test(() => {
assert_true('autocorrect' in document.createElement('div'));
}, "Test that the autocorrect attribute is available on div.");
test(() => {
assert_true('autocorrect' in document.createElement('form'));
}, "Test that the autocorrect attribute is available on form.");
test(() => {
[ document.createElement('input'),
document.createElement('textarea'),
document.createElement('div'),
document.createElement('form') ].forEach(e => {
e.autocorrect = true;
assert_true(e.autocorrect);
assert_equals(e.getAttribute('autocorrect'), 'on');
e.autocorrect = 'hello';
assert_true(e.autocorrect);
assert_equals(e.getAttribute('autocorrect'), 'on');
e.autocorrect = false;
assert_false(e.autocorrect);
assert_equals(e.getAttribute('autocorrect'), 'off');
e.autocorrect = 0;
assert_false(e.autocorrect);
assert_equals(e.getAttribute('autocorrect'), 'off');
});
}, "Test setting the autocorrect IDL attribute.");
test(() => {
[ document.createElement('input'),
document.createElement('textarea'),
document.createElement('div'),
document.createElement('form') ].forEach(e => {
e.setAttribute('autocorrect', 'on');
assert_equals(e.getAttribute('autocorrect'), 'on');
assert_true(e.autocorrect);
e.setAttribute('autocorrect', 'ON');
assert_equals(e.getAttribute('autocorrect'), 'ON');
assert_true(e.autocorrect);
e.setAttribute('autocorrect', 'off');
assert_equals(e.getAttribute('autocorrect'), 'off');
assert_false(e.autocorrect);
e.setAttribute('autocorrect', 'OFF');
assert_equals(e.getAttribute('autocorrect'), 'OFF');
assert_false(e.autocorrect);
e.setAttribute('autocorrect', 'invalid_value');
assert_equals(e.getAttribute('autocorrect'), 'invalid_value');
assert_true(e.autocorrect);
e.setAttribute('autocorrect', '');
assert_equals(e.getAttribute('autocorrect'), '');
assert_true(e.autocorrect);
e.removeAttribute('autocorrect');
assert_false(e.hasAttribute('autocorrect'));
assert_true(e.autocorrect);
});
}, "Test setting the autocorrect attribute using setAttribute.");
test(t => {
const testData = [
{
formValue: null,
formElementValue: null,
inheritedResult: true,
uninheritedResult: true
},
{
formValue: null,
formElementValue: 'on',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'on',
formElementValue: null,
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'on',
formElementValue: 'on',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: null,
formElementValue: 'off',
inheritedResult: false,
uninheritedResult: false
},
{
formValue: 'off',
formElementValue: null,
inheritedResult: false,
uninheritedResult: true
},
{
formValue: 'off',
formElementValue: 'off',
inheritedResult: false,
uninheritedResult: false
},
{
formValue: 'on',
formElementValue: 'off',
inheritedResult: false,
uninheritedResult: false
},
{
formValue: 'off',
formElementValue: 'on',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'off',
formElementValue: 'foo',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'off',
formElementValue: 'bar',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'off',
formElementValue: '',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'off',
formElementValue: '',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'on',
formElementValue: 'foo',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'on',
formElementValue: 'bar',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'on',
formElementValue: '',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'on',
formElementValue: '',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'foo',
formElementValue: 'off',
inheritedResult: false,
uninheritedResult: false
},
{
formValue: 'bar',
formElementValue: 'off',
inheritedResult: false,
uninheritedResult: false
},
{
formValue: '',
formElementValue: 'off',
inheritedResult: false,
uninheritedResult: false
},
{
formValue: '',
formElementValue: 'off',
inheritedResult: false,
uninheritedResult: false
},
{
formValue: 'foo',
formElementValue: 'on',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: 'bar',
formElementValue: 'on',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: '',
formElementValue: 'on',
inheritedResult: true,
uninheritedResult: true
},
{
formValue: '',
formElementValue: 'on',
inheritedResult: true,
uninheritedResult: true
}
];
const formElements = [
{element: 'button', inherits: true},
{element: 'fieldset', inherits: true},
{element: 'img', inherits: false},
{element: 'input', inherits: true},
{element: 'object', inherits: false},
{element: 'output', inherits: true},
{element: 'select', inherits: true},
{element: 'textarea', inherits: true},
];
const form = document.createElement('form');
form.id = 'form';
document.body.appendChild(form);
t.add_cleanup(() => form.remove());
testData.forEach(data => {
form.removeAttribute('autocorrect');
if (data.formValue !== null) {
form.setAttribute('autocorrect', data.formValue);
}
formElements.forEach(elementData => {
const element = document.createElement(elementData.element);
form.appendChild(element);
const element2 = document.createElement(elementData.element);
element2.setAttribute('form', 'form');
document.body.appendChild(element2);
t.add_cleanup(() => element2.remove());
if (data.formElementValue !== null) {
element.setAttribute('autocorrect', data.formElementValue);
element2.setAttribute('autocorrect', data.formElementValue);
}
const descriptionSuffix = 'with "' + data.formValue
+ '" and form element with "'+ data.formElementValue + '"';
if (elementData.inherits) {
assert_equals(element.autocorrect, data.inheritedResult,
`${elementData.element} element with form parent `
+ `${descriptionSuffix}`);
assert_equals(element2.autocorrect, data.inheritedResult,
`${elementData.element} element with form owner attribute`
+ ` set ${descriptionSuffix}`);
} else {
assert_equals(element.autocorrect, data.uninheritedResult,
`${elementData.element} element with form parent `
+ `${descriptionSuffix}`);
assert_equals(element2.autocorrect, data.uninheritedResult,
`${elementData.element} element with form owner attribute`
+ `set ${descriptionSuffix}`);
}
});
});
}, "Test inheriting autocorrection from a form.")
test(t => {
const editingHost = document.createElement("div");
const container = document.createElement("br");
editingHost.contentEditable = true;
editingHost.appendChild(container);
document.body.appendChild(editingHost);
t.add_cleanup(() => editingHost.remove());
editingHost.autocorrect = false;
container.autocorrect = true;
assert_false(editingHost.autocorrect);
assert_true(container.autocorrect);
editingHost.autocorrect = true;
container.autocorrect = false;
assert_true(editingHost.autocorrect);
assert_false(container.autocorrect);
}, "Test autocorrection in an editing host.")
test(t => {
const form = document.createElement("form");
const passwordInput = document.createElement("input");
const emailInput = document.createElement("input");
const urlInput = document.createElement("input");
const textInput = document.createElement("input");
passwordInput.type = "password";
emailInput.type = "email";
urlInput.type = "url";
form.setAttribute("autocorrect", "on");
document.body.appendChild(form);
t.add_cleanup(() => form.remove());
for (const input of [passwordInput, emailInput, urlInput, textInput])
form.appendChild(input);
assert_false(passwordInput.autocorrect, `Input of type ${passwordInput.type}`);
assert_false(emailInput.autocorrect, `Input of type ${emailInput.type}`);
assert_false(urlInput.autocorrect, `Input of type ${urlInput.type}`);
assert_true(textInput.autocorrect, `Input of type ${textInput.type}`);
}, "Test autocorrection in password, URL, and email inputs.")
</script>
</body>
</html>