diff --git a/fluent-dom/src/dom_localization.js b/fluent-dom/src/dom_localization.js index dc7e410e..d0366330 100644 --- a/fluent-dom/src/dom_localization.js +++ b/fluent-dom/src/dom_localization.js @@ -26,6 +26,8 @@ export default class DOMLocalization extends Localization { // A Set of DOM trees observed by the `MutationObserver`. this.roots = new Set(); + // The roots that use the observer. + this.rootsUsingObserver = new Set(); // requestAnimationFrame handler. this.pendingrAF = null; // list of elements pending for translation. @@ -121,8 +123,12 @@ export default class DOMLocalization extends Localization { * `newRoot` in order to translate mutations in it. * * @param {Element | DocumentFragment} newRoot - Root to observe. + * @param {{useObserver: boolean}} use { useObserver: false} to opt out the + * Mutation Observer for this root. True by default. */ - connectRoot(newRoot) { + connectRoot(newRoot, opts = {}) { + const useObserver = "useObserver" in opts ? opts.useObserver : true; // Defaults to true + for (const root of this.roots) { if ( root === newRoot || @@ -146,7 +152,10 @@ export default class DOMLocalization extends Localization { } this.roots.add(newRoot); - this.mutationObserver.observe(newRoot, this.observerConfig); + if (useObserver) { + this.rootsUsingObserver.add(newRoot); + this.mutationObserver.observe(newRoot, this.observerConfig); + } } /** @@ -162,9 +171,14 @@ export default class DOMLocalization extends Localization { * @returns {boolean} */ disconnectRoot(root) { + const rootUseObserver = this.rootsUsingObserver.has(root); this.roots.delete(root); - // Pause the mutation observer to stop observing `root`. - this.pauseObserving(); + + if (rootUseObserver) { + this.rootsUsingObserver.delete(root); + // Pause the mutation observer to stop observing `root`. + this.pauseObserving(); + } if (this.roots.size === 0) { this.mutationObserver = null; @@ -177,8 +191,10 @@ export default class DOMLocalization extends Localization { return true; } - // Resume observing all other roots. - this.resumeObserving(); + if (rootUseObserver) { + // Resume observing all other roots. + this.resumeObserving(); + } return false; } @@ -212,7 +228,7 @@ export default class DOMLocalization extends Localization { return; } - for (const root of this.roots) { + for (const root of this.rootsUsingObserver) { this.mutationObserver.observe(root, this.observerConfig); } } diff --git a/fluent-dom/test/dom_localization_test.js b/fluent-dom/test/dom_localization_test.js index 54effa56..9a4ba24c 100644 --- a/fluent-dom/test/dom_localization_test.js +++ b/fluent-dom/test/dom_localization_test.js @@ -39,3 +39,57 @@ suite("translateFragment", function () { assert.strictEqual(elem.textContent, "Original Value"); }); }); + +suite("connectRoot", function () { + // Wait for the MutationObserver callback and the requestAnimationFrame + // handler scheduled by `translateMutations`, then for the translation to + // be applied. + async function waitForMutationTranslation(domLoc) { + await new Promise(resolve => setTimeout(resolve, 0)); + await new Promise(resolve => requestAnimationFrame(resolve)); + await domLoc.formatMessages([]); + } + + test("translates mutations by default", async function () { + const domLoc = new DOMLocalization(["test.ftl"], mockGenerateMessages); + const root = document.createElement("div"); + document.body.appendChild(root); + + domLoc.connectRoot(root); + assert.ok(domLoc.rootsUsingObserver.has(root)); + + const elem = document.createElement("p"); + domLoc.setAttributes(elem, "key1"); + root.appendChild(elem); + + await waitForMutationTranslation(domLoc); + assert.strictEqual(elem.textContent, "Key 1"); + + domLoc.disconnectRoot(root); + root.remove(); + }); + + test("does not translate mutations when useObserver is false", async function () { + const domLoc = new DOMLocalization(["test.ftl"], mockGenerateMessages); + const root = document.createElement("div"); + document.body.appendChild(root); + + domLoc.connectRoot(root, { useObserver: false }); + assert.ok(domLoc.roots.has(root)); + assert.ok(!domLoc.rootsUsingObserver.has(root)); + + const elem = document.createElement("p"); + domLoc.setAttributes(elem, "key1"); + root.appendChild(elem); + + await waitForMutationTranslation(domLoc); + assert.strictEqual(elem.textContent, ""); + + // The root is still managed and can be translated explicitly. + await domLoc.translateRoots(); + assert.strictEqual(elem.textContent, "Key 1"); + + assert.strictEqual(domLoc.disconnectRoot(root), true); + root.remove(); + }); +});