Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions fluent-dom/src/dom_localization.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice that this loop is also quadratic when a lot of roots are added, and it's very unlikely for Lit-based apps, plus the drawbacks of not doing this check is very low if I understand this correctly (again, in a lit-based app where each root has a small DOM).

Maybe a better solution would be a separate localization class... I'll look into that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (
root === newRoot ||
Expand All @@ -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);
}
}

/**
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
}
}
Expand Down
54 changes: 54 additions & 0 deletions fluent-dom/test/dom_localization_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});