Bug 1389384 - Batch l10n mutations to one per paint. r?mossop
MozReview-Commit-ID: AbclA2lzTfT
--- a/intl/l10n/DOMLocalization.jsm
+++ b/intl/l10n/DOMLocalization.jsm
@@ -292,16 +292,21 @@ class DOMLocalization extends Localizati
* generator over MessageContexts
* @returns {DOMLocalization}
*/
constructor(windowElement, resourceIds, generateMessages) {
super(resourceIds, generateMessages);
// A Set of DOM trees observed by the `MutationObserver`.
this.roots = new Set();
+ // requestAnimationFrame handler.
+ this.pendingrAF = null;
+ // list of elements pending for translation.
+ this.pendingElements = new Set();
+ this.windowElement = windowElement;
this.mutationObserver = new windowElement.MutationObserver(
mutations => this.translateMutations(mutations)
);
this.observerConfig = {
attribute: true,
characterData: false,
childList: true,
@@ -425,17 +430,17 @@ class DOMLocalization extends Localizati
/**
* Translate all roots associated with this `DOMLocalization`.
*
* @returns {Promise}
*/
translateRoots() {
const roots = Array.from(this.roots);
return Promise.all(
- roots.map(root => this.translateFragment(root))
+ roots.map(root => this.translateElements(this.getTranslatables(root)))
);
}
/**
* Pauses the `MutationObserver`.
*
* @private
*/
@@ -459,72 +464,71 @@ class DOMLocalization extends Localizati
* Translate mutations detected by the `MutationObserver`.
*
* @private
*/
translateMutations(mutations) {
for (const mutation of mutations) {
switch (mutation.type) {
case 'attributes':
- this.translateElement(mutation.target);
+ this.pendingElements.add(mutation.target);
break;
case 'childList':
for (const addedNode of mutation.addedNodes) {
if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
if (addedNode.childElementCount) {
- this.translateFragment(addedNode);
+ for (let element of this.getTranslatables(addedNode)) {
+ this.pendingElements.add(element);
+ }
} else if (addedNode.hasAttribute(L10NID_ATTR_NAME)) {
- this.translateElement(addedNode);
+ this.pendingElements.add(addedNode);
}
}
}
break;
}
}
+
+ // This fragment allows us to coalesce all pending translations into a single
+ // requestAnimationFrame.
+ if (this.pendingElements.size > 0) {
+ if (this.pendingrAF === null) {
+ this.pendingrAF = this.windowElement.requestAnimationFrame(() => {
+ this.translateElements(Array.from(this.pendingElements));
+ this.pendingElements.clear();
+ this.pendingrAF = null;
+ });
+ }
+ }
}
/**
* Translate a DOM element or fragment asynchronously using this
* `DOMLocalization` object.
*
- * Manually trigger the translation (or re-translation) of a DOM fragment.
+ * Manually trigger the translation (or re-translation) of a list of elements.
* Use the `data-l10n-id` and `data-l10n-args` attributes to mark up the DOM
* with information about which translations to use.
*
* Returns a `Promise` that gets resolved once the translation is complete.
*
- * @param {DOMFragment} frag - Element or DocumentFragment to be translated
+ * @param {Array<Element>} elements - List of elements to be translated
* @returns {Promise}
*/
- async translateFragment(frag) {
- const elements = this.getTranslatables(frag);
+ async translateElements(elements) {
if (!elements.length) {
return undefined;
}
const keys = elements.map(this.getKeysForElement);
const translations = await this.formatMessages(keys);
return this.applyTranslations(elements, translations);
}
/**
- * Translate a single DOM element asynchronously.
- *
- * Returns a `Promise` that gets resolved once the translation is complete.
- *
- * @param {Element} element - HTML element to be translated
- * @returns {Promise}
- */
- async translateElement(element) {
- const translations =
- await this.formatMessages([this.getKeysForElement(element)]);
- return this.applyTranslations([element], translations);
- }
-
- /**
* Applies translations onto elements.
*
* @param {Array<Element>} elements
* @param {Array<Object>} translations
* @private
*/
applyTranslations(elements, translations) {
this.pauseObserving();
--- a/intl/l10n/test/test_domlocalization.js
+++ b/intl/l10n/test/test_domlocalization.js
@@ -2,14 +2,13 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
const { DOMLocalization } =
Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
add_task(function test_methods_presence() {
equal(typeof DOMLocalization.prototype.getAttributes, "function");
equal(typeof DOMLocalization.prototype.setAttributes, "function");
- equal(typeof DOMLocalization.prototype.translateElement, "function");
- equal(typeof DOMLocalization.prototype.translateFragment, "function");
+ equal(typeof DOMLocalization.prototype.translateElements, "function");
equal(typeof DOMLocalization.prototype.connectRoot, "function");
equal(typeof DOMLocalization.prototype.disconnectRoot, "function");
equal(typeof DOMLocalization.prototype.translateRoots, "function");
});