mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
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:
parent
85a3b8c2f6
commit
760d4933e4
@ -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.
|
||||
*
|
||||
|
@ -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 */ `
|
||||
|
Loading…
Reference in New Issue
Block a user