Bug 1976006 - Fix moz- input labels when localisation changes a=RyanVM

Original Revision: https://phabricator.services.mozilla.com/D258995

Differential Revision: https://phabricator.services.mozilla.com/D263495
This commit is contained in:
Mark Striemer
2025-09-03 23:57:28 +00:00
committed by rvandermeulen@mozilla.com
parent b6ae9b266e
commit cd516e806e
4 changed files with 132 additions and 4 deletions

View File

@@ -93,6 +93,29 @@
"support link created" "support link created"
); );
}); });
add_task(async function testLabelChange() {
let el = await renderTemplate();
let labelEl = el.shadowRoot.querySelector("label");
is(labelEl.innerText.trim(), "Example label!", "Label is correct");
is(labelEl.getAttribute("shownaccesskey"), "x", "Accesskey is shown");
// Test changing the label text, this could happen when the locale changes.
el.label = "Example new label!";
await el.updateComplete;
is(
labelEl.innerText.trim(),
"Example new label!",
"Label is updated correctly"
);
is(
labelEl.getAttribute("shownaccesskey"),
"x",
"Accesskey is still shown"
);
});
</script> </script>
</head> </head>
<body> <body>

View File

@@ -48,6 +48,13 @@
Shownaccesskey highlights the key Shownaccesskey highlights the key
<input type="checkbox" /> <input type="checkbox" />
</label> </label>
<label is="moz-label" accesskey="t" class="test-changetext"
><span class="text">It has text</span></label
>
<label is="moz-label" shownaccesskey="t" class="test-changetext"
><span class="text">It has text</span></label
>
</div> </div>
<script class="testbody" type="application/javascript"> <script class="testbody" type="application/javascript">
let labels = document.querySelectorAll("label[is='moz-label']"); let labels = document.querySelectorAll("label[is='moz-label']");
@@ -101,7 +108,7 @@
add_task(async function testAccesskeyFocus() { add_task(async function testAccesskeyFocus() {
labels.forEach(label => { labels.forEach(label => {
let accessKey = label.getAttribute("accesskey"); let accessKey = label.getAttribute("accesskey");
if (!accessKey) { if (!accessKey || label.classList.contains("test-changetext")) {
return; return;
} }
// Find the labelled element via the "for" attr if there's an ID // Find the labelled element via the "for" attr if there's an ID
@@ -206,6 +213,70 @@
"The associated input is not triggered by accesskey." "The associated input is not triggered by accesskey."
); );
}); });
add_task(async function testAccesskeyTextContentChange() {
const ORIG_TEXT = "It has text";
const NEW_TEXT = "New text";
const ACCESSKEY = "t";
let [accesskeyLabel, shownaccesskeyLabel] =
document.querySelectorAll(".test-changetext");
is(
accesskeyLabel.textContent,
ORIG_TEXT,
"Original text is correct accesskey"
);
is(
shownaccesskeyLabel.textContent,
ORIG_TEXT,
"Original text is correct shownaccesskey"
);
let getWrapper = label => label.querySelector(".accesskey");
is(
getWrapper(accesskeyLabel).textContent,
ACCESSKEY,
"Original accesskey character is wrapped accesskey"
);
is(
getWrapper(shownaccesskeyLabel).textContent,
ACCESSKEY,
"Original accesskey character is wrapped accesskey"
);
// Set the textContent on the inner span so we don't hit `set textContent`
accesskeyLabel.firstElementChild.textContent = NEW_TEXT;
is(
accesskeyLabel.textContent,
NEW_TEXT,
"Updated text is correct accesskey"
);
ok(!getWrapper(accesskeyLabel), "Accesskey span was removed accesskey");
// formatAccessKey waits a tick so we don't get infinite observer looping
await new Promise(r => queueMicrotask(r));
is(
getWrapper(accesskeyLabel).textContent,
ACCESSKEY,
"Updated accesskey character is wrapped accesskey"
);
shownaccesskeyLabel.firstElementChild.textContent = NEW_TEXT;
is(
shownaccesskeyLabel.textContent,
NEW_TEXT,
"Updated text is correct shownaccesskey"
);
ok(
!getWrapper(shownaccesskeyLabel),
"Accesskey span was removed shownaccesskey"
);
// formatAccessKey waits a tick so we don't get infinite observer looping
await new Promise(r => queueMicrotask(r));
is(
getWrapper(shownaccesskeyLabel).textContent,
ACCESSKEY,
"Updated accesskey character is wrapped accesskey"
);
});
</script> </script>
</body> </body>
</html> </html>

View File

@@ -419,7 +419,12 @@ export class MozBaseInputElement extends MozLitElement {
if (!this.label) { if (!this.label) {
return ""; return "";
} }
return html`${this.iconTemplate()}<span class="text">${this.label}</span>`; return html`<span class="text-container"
>${this.iconTemplate()}<span
class="text"
.textContent=${this.label}
></span
></span>`;
} }
descriptionTemplate() { descriptionTemplate() {

View File

@@ -65,19 +65,39 @@ class MozTextLabel extends HTMLLabelElement {
} }
} }
#startMutationObserver() {
if (!this.#observer) {
return;
}
this.#observer.observe(this, {
characterData: true,
childList: true,
subtree: true,
});
}
#stopMutationObserver() {
if (!this.#observer) {
return;
}
this.#observer.disconnect();
}
connectedCallback() { connectedCallback() {
this.#setStyles(); this.#setStyles();
this.formatAccessKey(); this.formatAccessKey();
if (!this.#observer) { if (!this.#observer) {
this.#observer = new MutationObserver(() => { this.#observer = new MutationObserver(() => {
this.#lastFormattedAccessKey = null;
this.formatAccessKey(); this.formatAccessKey();
}).observe(this, { characterData: true, childList: true, subtree: true }); });
this.#startMutationObserver();
} }
} }
disconnectedCallback() { disconnectedCallback() {
if (this.#observer) { if (this.#observer) {
this.#observer.disconnect(); this.#stopMutationObserver();
this.#observer = null; this.#observer = null;
} }
} }
@@ -185,6 +205,15 @@ class MozTextLabel extends HTMLLabelElement {
) { ) {
return; return;
} }
this.#stopMutationObserver();
try {
this.#formatAccessKey(accessKey);
} finally {
queueMicrotask(() => this.#startMutationObserver());
}
}
#formatAccessKey(accessKey) {
this.#lastFormattedAccessKey = accessKey; this.#lastFormattedAccessKey = accessKey;
if (this.accessKeySpan) { if (this.accessKeySpan) {
// Clear old accesskey // Clear old accesskey