Bug 1928430 - Fix translated select elements changing values; r=translations-reviewers,nordzilla

Differential Revision: https://phabricator.services.mozilla.com/D229538
This commit is contained in:
Greg Tatum 2024-11-21 14:41:40 +00:00
parent 85a3b8c2f6
commit 760d4933e4
2 changed files with 105 additions and 6 deletions

View File

@ -1973,10 +1973,6 @@ function isNodeInViewport(node) {
* @returns {void}
*/
function updateElement(translationsDocument, element) {
if (element.tagName === "OPTION" && !element.hasAttribute("value")) {
// This is an implicit option value. Make it explicit before translating it.
element.setAttribute("value", element.value);
}
// This text should have the same layout as the target, but it's not completely
// guaranteed since the content page could change at any time, and the translation process is async.
//
@ -1996,8 +1992,51 @@ function updateElement(translationsDocument, element) {
*/
const clonedNodes = new Set();
// Guard against unintended changes to the "value" of <option> elements during
// translation. This issue occurs because if an <option> element lacks an explicitly
// set "value" attribute, then the default "value" will be taken from the text content
// when requested.
//
// For example, <option>dog</option> might be translated to <option>perro</option>.
// Without an explicit "value", the implicit "value" would change from "dog" to "perro",
// and this can cause problems for submissions to queries etc.
//
// To prevent this, we ensure every translated <option> has an explicit "value"
// attribute, either preserving the original "value" or assigning it from the original
// text content. This results in <option>dog</option> being translated to
// <option value="dog">perro</option>
//
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option#value
if (element.tagName === "OPTION") {
element.setAttribute("value", element.value);
}
for (const option of element.querySelectorAll("option")) {
option.setAttribute("value", option.value);
}
/**
* Build up a mapping of any element that has a "value" field that may change based
* on translations. In the recursive "merge" function below, we can remove <option>
* elements from <select> elements, which could cause the value attribute to change
* as the option is removed. This will need to be restored.
*
* @type {Map<Node, string>}
*/
const nodeValues = new Map();
for (const select of element.querySelectorAll("select")) {
nodeValues.set(select, select.value);
}
merge(element, translationsDocument.body.firstChild);
// Restore the <select> values.
if (element.tagName === "SELECT") {
element.value = nodeValues.get(element);
}
for (const select of element.querySelectorAll("select")) {
select.value = nodeValues.get(select);
}
/**
* Merge the live tree with the translated tree by re-using elements from the live tree.
*

View File

@ -1052,7 +1052,7 @@ add_task(async function test_option_values() {
<select>
<option>Red</option>
<option>Orange</option>
<option>Yellow</option>
<option selected="">Yellow</option>
<option value="Green">Green</option>
<option value="Blue">Blue</option>
<option value="Purple">Purple</option>
@ -1067,7 +1067,7 @@ add_task(async function test_option_values() {
<select>
<option value="Red">RED</option>
<option value="Orange">ORANGE</option>
<option value="Yellow">YELLOW</option>
<option selected="" value="Yellow">YELLOW</option>
<option value="Green">GREEN</option>
<option value="Blue">BLUE</option>
<option value="Purple">PURPLE</option>
@ -1078,6 +1078,66 @@ add_task(async function test_option_values() {
cleanup();
});
add_task(async function test_option_values() {
const { document, translate, htmlMatches, cleanup } =
await createTranslationsDoc(/* html */ `
<span>
<select>
<option>unconfirmed</option>
<option selected="">new</option>
<option>assigned</option>
<option>resolved</option>
</select>
</span>
`);
const select = document.querySelector("select");
document.querySelector("select").addEventListener("change", () => {
ok(false, "The change event should not ever be fired.");
});
is(document.querySelector("select").value, "new", 'The "new" value selected');
translate();
await htmlMatches(
"Option values are not changed",
/* html */ `
<span>
<select>
<option value="unconfirmed">
UNCONFIRMED
</option>
<option selected="" value="new">
NEW
</option>
<option value="assigned">
ASSIGNED
</option>
<option value="resolved">
RESOLVED
</option>
</select>
</span>
`
);
is(
document.querySelector("select").value,
"new",
'After translation the "new" value is still selected'
);
is(
document.querySelector("select"),
select,
"The original select element is still present"
);
cleanup();
});
add_task(async function test_basic_attributes() {
const { translate, htmlMatches, cleanup } =
await createTranslationsDoc(/* html */ `