Bug 1389384 - Batch l10n mutations to one per paint. r=mossop
☠☠ backed out by 2ddc9ad2b919 ☠ ☠
authorZibi Braniecki <zbraniecki@mozilla.com>
Sat, 16 Dec 2017 21:42:58 -0600
changeset 453407 67f5e518f5751d7d037db95d5d7e705d04d1752e
parent 453406 1c4243ce6d43af3b737b9c1590a9f33f684c784d
child 453408 7902f47451a1d405e4123631be7b6c432bfd2092
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmossop
bugs1389384
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1389384 - Batch l10n mutations to one per paint. r=mossop MozReview-Commit-ID: AbclA2lzTfT
intl/l10n/DOMLocalization.jsm
--- 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();