mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-20 16:28:54 +00:00
LibWeb: Implement autocorrect attribute
This commit is contained in:
parent
a6fb7c84e9
commit
829437c11d
Notes:
github-actions[bot]
2025-08-29 14:48:21 +00:00
Author: https://github.com/Calme1709
Commit: 829437c11d
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4907
Reviewed-by: https://github.com/Lubrsi ✅
Reviewed-by: https://github.com/ananas-dev
6 changed files with 428 additions and 1 deletions
|
@ -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") \
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue