LibWeb: Implement the translate attribute

This commit is contained in:
Callum Law 2025-05-23 22:36:33 +12:00 committed by Jelle Raaijmakers
commit 279913a223
Notes: github-actions[bot] 2025-05-23 12:35:07 +00:00
24 changed files with 399 additions and 1 deletions

View file

@ -3327,6 +3327,36 @@ IntersectionObserver::IntersectionObserverRegistration& Element::get_intersectio
return *registration_iterator;
}
// https://html.spec.whatwg.org/multipage/dom.html#translation-mode
Element::TranslationMode Element::translation_mode() const
{
// Each element (even non-HTML elements) has a translation mode, which is in either the translate-enabled state or
// the no-translate state.
// If an HTML element's translate attribute is in the Yes state, then the element's translation mode is in the
// translate-enabled state;
// NOTE: The attribute is in the Yes state if the attribute is present and its value is the empty string or is a
// ASCII-case-insensitive match for "yes".
auto maybe_translate_attribute = attribute(HTML::AttributeNames::translate);
if (maybe_translate_attribute.has_value() && (maybe_translate_attribute.value().is_empty() || maybe_translate_attribute.value().equals_ignoring_ascii_case("yes"sv)))
return TranslationMode::TranslateEnabled;
// otherwise, if the element's translate attribute is in the No state, then the element's translation mode is in
// the no-translate state.
if (maybe_translate_attribute.has_value() && maybe_translate_attribute.value().equals_ignoring_ascii_case("no"sv)) {
return TranslationMode::NoTranslate;
}
// Otherwise, either the element's translate attribute is in the Inherit state, or the element is not an HTML
// element and thus does not have a translate attribute; in either case, the element's translation mode is in the
// same state as its parent element's, if any.
if (auto parent = parent_element())
return parent->translation_mode();
// or in the translate-enabled state, if the element's parent element is null
return TranslationMode::TranslateEnabled;
}
// https://html.spec.whatwg.org/multipage/dom.html#the-directionality
Element::Directionality Element::directionality() const
{

View file

@ -378,6 +378,12 @@ public:
CSSPixelPoint scroll_offset(ScrollOffsetFor type) const { return m_scroll_offset[to_underlying(type)]; }
void set_scroll_offset(ScrollOffsetFor type, CSSPixelPoint offset) { m_scroll_offset[to_underlying(type)] = offset; }
enum class TranslationMode {
TranslateEnabled,
NoTranslate
};
TranslationMode translation_mode() const;
enum class Dir {
Ltr,
Rtl,

View file

@ -299,6 +299,7 @@ namespace AttributeNames {
__ENUMERATE_HTML_ATTRIBUTE(text, "text") \
__ENUMERATE_HTML_ATTRIBUTE(title, "title") \
__ENUMERATE_HTML_ATTRIBUTE(topmargin, "topmargin") \
__ENUMERATE_HTML_ATTRIBUTE(translate, "translate") \
__ENUMERATE_HTML_ATTRIBUTE(truespeed, "truespeed") \
__ENUMERATE_HTML_ATTRIBUTE(type, "type") \
__ENUMERATE_HTML_ATTRIBUTE(usemap, "usemap") \

View file

@ -104,6 +104,25 @@ bool HTMLElement::is_potentially_render_blocking()
return is_implicitly_potentially_render_blocking();
}
// https://html.spec.whatwg.org/multipage/dom.html#dom-translate
bool HTMLElement::translate() const
{
// The translate IDL attribute must, on getting, return true if the element's translation mode is
// translate-enabled, and false otherwise
return translation_mode() == TranslationMode::TranslateEnabled;
}
// https://html.spec.whatwg.org/multipage/dom.html#dom-translate
void HTMLElement::set_translate(bool new_value)
{
// On setting, it must set the content attribute's value to "yes" if the new value is true, and set the content
// attribute's value to "no" otherwise.
if (new_value)
MUST(set_attribute(HTML::AttributeNames::translate, "yes"_string));
else
MUST(set_attribute(HTML::AttributeNames::translate, "no"_string));
}
// https://html.spec.whatwg.org/multipage/dom.html#dom-dir
StringView HTMLElement::dir() const
{

View file

@ -82,6 +82,9 @@ public:
Optional<String> title() const { return attribute(HTML::AttributeNames::title); }
bool translate() const;
void set_translate(bool);
StringView dir() const;
void set_dir(String const&);

View file

@ -14,7 +14,7 @@ interface HTMLElement : Element {
// metadata attributes
[Reflect, CEReactions] attribute DOMString title;
[Reflect, CEReactions] attribute DOMString lang;
[FIXME, CEReactions] attribute boolean translate;
[CEReactions] attribute boolean translate;
[CEReactions] attribute DOMString dir;
// user interaction

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass In the default case, ie. with no translate attribute in the page, javascript will detect the translation mode of text as translate-enabled.

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass If the translate attribute is set to yes, javascript will detect the translation mode of text as translate-enabled.

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass If the translate attribute is set to no, javascript will detect the translation mode of text as no-translate.

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass If the translate attribute is set to no, javascript will detect the translation mode of elements inside that element with no translate flag as no-translate.

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass If the translate attribute is set to yes on an element inside an element with the translate attribute set to no, javascript will detect the translation mode of text in the inner element as translate-enabled.

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass If the translate attribute is set to a null string, javascript will detect the translation mode of text as translate-enabled.

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass keyword yes

View file

@ -0,0 +1,10 @@
Harness status: OK
Found 5 tests
5 Pass
Pass No parent node
Pass DocumentFragment parent node
Pass ShadowRoot parent node whose shadow host has translate=yes
Pass ShadowRoot parent node whose shadow host has translate=no
Pass Document parent node

View file

@ -0,0 +1,8 @@
Harness status: OK
Found 3 tests
3 Pass
Pass Non-HTML elements default to translate-enabled
Pass Non-HTML elements inherit their parent's translation-enabled state
Pass Non-HTML elements inherit their parent's no-translation state

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="utf-8"/>
<title>no translate attribute</title>
<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
<link rel='help' href='https://html.spec.whatwg.org/multipage/#the-translate-attribute'>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<style type='text/css'>
</style>
</head>
<body>
<div class="test"><div id="box">&nbsp;</div></div>
<script>
test(function() {
assert_true(document.getElementById('box').translate);
}, "In the default case, ie. with no translate attribute in the page, javascript will detect the translation mode of text as translate-enabled.");
</script>
<div id='log'></div>
</body>
</html>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="utf-8"/>
<title>translate=yes</title>
<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
<link rel='help' href='https://html.spec.whatwg.org/multipage/#the-translate-attribute'>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<style type='text/css'>
</style>
</head>
<body>
<div class="test"><div id="box" translate="yes">&nbsp;</div></div>
<script>
test(function() {
assert_true(document.getElementById('box').translate);
}, "If the translate attribute is set to yes, javascript will detect the translation mode of text as translate-enabled.");
</script>
<div id='log'></div>
</body>
</html>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="utf-8"/>
<title>translate=no</title>
<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
<link rel='help' href='https://html.spec.whatwg.org/multipage/#the-translate-attribute'>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<style type='text/css'>
</style>
</head>
<body>
<div class="test"><div id="box" translate="no">&nbsp;</div></div>
<script>
test(function() {
assert_false(document.getElementById('box').translate);
}, "If the translate attribute is set to no, javascript will detect the translation mode of text as no-translate.");
</script>
<div id='log'></div>
</body>
</html>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="utf-8"/>
<title>translate inherits no</title>
<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
<link rel='help' href='https://html.spec.whatwg.org/multipage/#the-translate-attribute'>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<style type='text/css'>
</style>
</head>
<body>
<div class="test"><div id="box" translate="no">&nbsp; <span id="spantest">&nbsp;</span></div></div>
<script>
test(function() {
assert_false(document.getElementById('spantest').translate);
}, "If the translate attribute is set to no, javascript will detect the translation mode of elements inside that element with no translate flag as no-translate.");
</script>
<div id='log'></div>
</body>
</html>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="utf-8"/>
<title>translate=yes inside translate=no</title>
<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
<link rel='help' href='https://html.spec.whatwg.org/multipage/#the-translate-attribute'>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<style type='text/css'>
</style>
</head>
<body>
<div class="test"><div id="box" translate="no">&nbsp; <span id="spantest" translate="yes">&nbsp;</span></div></div>
<script>
test(function() {
assert_true(document.getElementById('spantest').translate);
}, "If the translate attribute is set to yes on an element inside an element with the translate attribute set to no, javascript will detect the translation mode of text in the inner element as translate-enabled.");
</script>
<div id='log'></div>
</body>
</html>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="utf-8"/>
<title>translate=""</title>
<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'>
<link rel='help' href='https://html.spec.whatwg.org/multipage/#the-translate-attribute'>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<style type='text/css'>
</style>
</head>
<body>
<div class="test" translate="no"><div id="box" translate="">&nbsp;</div></div>
<script>
test(function() {
assert_true(document.getElementById('box').translate);
}, "If the translate attribute is set to a null string, javascript will detect the translation mode of text as translate-enabled.");
</script>
<div id='log'></div>
</body>
</html>

View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="help" href="https://html.spec.whatwg.org/#attr-translate">
<link rel="help" href="https://html.spec.whatwg.org/#enumerated-attribute">
<meta name="assert" content="@translate values are ASCII case-insensitive">
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<!--
We wrap the <span> elements under test with <div> elements so the invalid
value default (inherit) can be distinguished from true through the IDL
attribute. The inherit state would otherwise yield true because inheritance
would go all the way to :root, whose translation mode is translate-enabled
because its also in the inherit state.
-->
<div translate="no"><span translate="yes"></span></div>
<div translate="no"><span translate="YeS"></span></div>
<div translate="no"><span translate="yeſ"></span></div>
<script>
const span = document.querySelectorAll("span");
test(() => {
assert_equals(span[0].translate, true, "lowercase valid");
assert_equals(span[1].translate, true, "mixed case valid");
assert_equals(span[2].translate, false, "non-ASCII invalid");
}, "keyword yes");
</script>

View file

@ -0,0 +1,33 @@
<!doctype html>
<meta charset=utf-8>
<title>The translate attribute inherit state when there's no parent element</title>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script>
test(() => {
const div = document.createElement("div");
assert_true(div.translate);
}, 'No parent node');
test(() => {
const div = document.createElement("div");
const frag = document.createDocumentFragment();
frag.append(div);
assert_true(div.translate);
}, 'DocumentFragment parent node');
for (const translateValue of ['yes', 'no']) {
test(() => {
const div = document.createElement("div");
const myElement = document.createElement("my-element");
myElement.setAttribute('translate', translateValue);
myElement.attachShadow({mode: 'open'});
myElement.shadowRoot.append(div);
assert_true(div.translate);
}, `ShadowRoot parent node whose shadow host has translate=${translateValue}`);
}
test(() => {
assert_true(document.documentElement.translate);
}, 'Document parent node');
</script>

View file

@ -0,0 +1,46 @@
<!doctype html>
<meta charset=utf-8>
<title>Non-HTML elements have a translation mode</title>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script>
test(() => {
const svgContainer = document.createElement("svg");
const foreignObject = document.createElement("foreignObject");
svgContainer.appendChild(foreignObject);
const div = document.createElement("div");
foreignObject.appendChild(div);
assert_true(div.translate);
}, 'Non-HTML elements default to translate-enabled');
test(() => {
const outerDiv = document.createElement("div");
outerDiv.translate = true;
assert_true(outerDiv.translate);
const svgContainer = document.createElement("svg");
outerDiv.appendChild(svgContainer);
const foreignObject = document.createElement("foreignObject");
svgContainer.appendChild(foreignObject);
const div = document.createElement("div");
foreignObject.appendChild(div);
assert_true(div.translate);
}, "Non-HTML elements inherit their parent's translation-enabled state");
test(() => {
const outerDiv = document.createElement("div");
outerDiv.translate = false;
assert_false(outerDiv.translate);
const svgContainer = document.createElement("svg");
outerDiv.appendChild(svgContainer);
const foreignObject = document.createElement("foreignObject");
svgContainer.appendChild(foreignObject);
const div = document.createElement("div");
foreignObject.appendChild(div);
assert_false(div.translate);
}, "Non-HTML elements inherit their parent's no-translation state");
</script>