-
Notifications
You must be signed in to change notification settings - Fork 81
Description
See the bugzilla bug here => https://bugzilla.mozilla.org/show_bug.cgi?id=1988776
STR:
- Open the attachment of that bug
- Click the button at the top
and note that it takes ~1 seconde to update.
The source code for this is https://github.com/julienw/mutationobserver-issue
The context: I'm building a Lit-based app using mozilla-central MozLitElement. This is a wrapper on the upstream LitElement
class that handles localization through Fluent by using Fluent's connectRoot on every shadow root: https://searchfox.org/firefox-main/rev/938e8f38c6765875e998d5c2965ad5864f5a5ee2/toolkit/content/widgets/lit-utils.mjs#160
This in turns calls observe
on this root:
fluent.js/fluent-dom/src/dom_localization.js
Line 149 in 9a18331
this.mutationObserver.observe(newRoot, this.observerConfig); |
What happens is that Lit updates the page by removing or adding some nodes.
Then a lot of these nodes are registering or unregistering themselves with Fluent as roots.
When the node is removed from the DOM, disconnectRoot
is called, then it self calls observer.disconnect()
(from pauseObserving
) then observer.observe()
(from resumeObserving
) on all roots again. And we end up doing that a lot as a result of lit removing elements. The testcase shows this case when it displays less rows than the previous step.
Also applyTranslations
does that, which means we do that once per root that has a change. The testcase shows this case when it displays more rows than the previous step.
So if I'm not wrong the complexity is n².
Now if inside the MutationObserver
implementation itself there's another loop (for example in getReceiverFor
), does that make it n³?
Possible solutions:
- change the MutationObserver API to expose an
unobserve
method so that we can unobserve just one root. But this would need a spec change :/ - have one MutationObserver object for each root. (but I recall a performance issue when doing that for ResizeObserver, so we might have surprises doing that too)
- instead of
disconnect
andobserve
-ing again, use a local boolean state that would be used inside the MutationObserver callback. One drawback is that when translating a large page, Firefox will collect all changes to call the callback, that would be discarded, and that might be a performance issue. Also I'm not sure of the timing of calling the MutationObserver callback and so this could be prone to races.
For now I fixed my issue by, instead of adding/removing nodes, controlling their CSS display
property. Moving forward it could be a good idea to integrate into Lit's localization library in https://github.com/lit/lit/tree/main/packages/localize-tools/src/formatters. Another option would be to have a converter from FTL files to XLIFF files (but I don't know if all features of FTL are convertible to XLIFF) so that the Lit machinery could consome them.