Clean up Localization initialization
Categories
(Core :: Internationalization, task, P1)
Tracking
()
Tracking | Status | |
---|---|---|
firefox77 | --- | fixed |
People
(Reporter: zbraniecki, Assigned: zbraniecki)
References
Details
Attachments
(8 files, 2 obsolete files)
47 bytes,
text/x-phabricator-request
|
Details | Review | |
47 bytes,
text/x-phabricator-request
|
Details | Review | |
47 bytes,
text/x-phabricator-request
|
Details | Review | |
47 bytes,
text/x-phabricator-request
|
Details | Review | |
47 bytes,
text/x-phabricator-request
|
Details | Review | |
47 bytes,
text/x-phabricator-request
|
Details | Review | |
47 bytes,
text/x-phabricator-request
|
Details | Review | |
47 bytes,
text/x-phabricator-request
|
Details | Review |
As a follow-up to bug 1621674 and before bug 1613705, I'd like to clean up the initialization of the Localization class (and DOMLocalization and DocumentL10n) in order to enable better integration with Web Components.
In particular, we currently have a gap in our coverage where if the document doesn't have l10n links, but web component injects one, it's possible that document.l10n
won't be enabled until DOMContentLoaded.
In the new model, I'd like to make it clear that construction of Localization
doesn't "enable" it. There's a separate Init
method that has to be called and only then translateMessages
, translateValue
and translateValues
becomes operational.
This means that right after initial link injection document.l10n
becomes available, but only connectRoot
, disconnectRoot
etc. can be called on it. Any attempt to translateFragment
or translateElements
or translateRoots
will fail.
Next, when initial l10n container is parsed (</linkset>
or </head>
), we call Init
and then all methods are available, but the initial translateRoots
happens only after parsing of the document completes.
If the link is injected after that moment, it'll skip the initial translation completely.
In order to make that logic work, DocumentL10n
gets a method overload - connectRoot
has a second param, which when set to true
means "perform initial translation as soon as possible".
Assignee | ||
Updated•5 years ago
|
Assignee | ||
Updated•5 years ago
|
Assignee | ||
Comment 1•5 years ago
|
||
Assignee | ||
Comment 2•5 years ago
|
||
Depends on D69900
Assignee | ||
Comment 3•5 years ago
|
||
With the following patches I was able to apply this:
diff --git a/browser/components/migration/content/migration.xhtml b/browser/components/migration/content/migration.xhtml
--- a/browser/components/migration/content/migration.xhtml
+++ b/browser/components/migration/content/migration.xhtml
@@ -11,19 +11,16 @@
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
windowtype="Browser:MigrationWizard"
title="&migrationWizard.title;"
onload="MigrationWizard.init()"
onunload="MigrationWizard.uninit()"
style="width: 40em;"
buttons="accept,cancel">
-<linkset>
- <html:link rel="localization" href="toolkit/global/wizard.ftl"/>
-</linkset>
<script src="chrome://global/content/customElements.js"/>
<script src="chrome://browser/content/migration/migration.js"/>
<wizard data-branded="true">
<wizardpage id="importSource" pageid="importSource" next="selectProfile"
label="&importSource.title;">
#ifdef XP_WIN
diff --git a/toolkit/content/widgets/wizard.js b/toolkit/content/widgets/wizard.js
--- a/toolkit/content/widgets/wizard.js
+++ b/toolkit/content/widgets/wizard.js
@@ -520,16 +520,17 @@
class MozWizardButtons extends MozXULElement {
connectedCallback() {
this._wizard = this.getRootNode().host;
this.textContent = "";
this.appendChild(MozXULElement.parseXULToFragment(this._markup));
MozXULElement.insertFTLIfNeeded("toolkit/global/wizard.ftl");
+ document.l10n.connectRoot(this, true);
this._wizardButtonDeck = this.querySelector(".wizard-next-deck");
this.initializeAttributeInheritance();
const listeners = [
["back", () => this._wizard.rewind()],
["next", () => this._wizard.advance()],
and it just works. The reasons why, are:
insertFTLIfNeeded
now synchronously enablesdocument.l10n
.DocumentL10n::ConnectRoot
overridesDOMLocalization:ConnectRoot
and adds a second argument which triggers the "translate when ready" mode.
The (2) is something that I need to be able to trigger this callback at any point - either while the document is still parsing (and then the root will be translated together with other roots later) or after the initial translation completed (in which case I trigger the translation of the new root immediately).
Assignee | ||
Comment 4•5 years ago
|
||
It's going to be quite tricky to get this reviewed as the knowledge o the components is fairly thinly spread between Smaug, Mossop and Stas.
I'll start with the big picture summary of the goals (read comment 0 for more details):
Part 1 (Localization):
*) Separate construction from initialization for DocumentL10n to allow for mRoots/MutationObserver/resourceIds to be adjusted before the initial translation. Notice, that this does not affect manually constructed new Localization
or new DOMLocalization
, just ones constructed from DocumentL10n
.
*) Narrow down the XPIDL interface for Localization.jsm - this is the second (after bug 1621674) step to get us to remove the JSM file. I kept the core logic there to minimize the patch size, and will handle the final move later.
*) Clean up the eager
flag which can be only set by DocumentL10n, and custom created DOMLocalization
or Localization
don't have access to it.
*) Separate out AddResourceIds
as the only place where ids are added. Thanks to separation of the constructor from init
resources that are meant to be loaded in initialization phase can be now added via regular AddResourceIds
.
Part 2 (DocumentL10n):
*) Enable document.l10n
after the first link is loaded, not after l10n links container is parsed
*) Remove the mPendingInitialTranslation
and mL10nResources
because now we have mDocumentL10n
available immediately so we can consult its state and store resources in it already.
*) Vastly simplify the logic in LocalizationLinkAdded
*) Add an overridden ConnectRoot
on DocumentL10n
that takes a second argument which indicates that if the root has been added after initial translation is completed, it should trigger its translation (otherwise the translation will happen when the document is done parsing)
Assignee | ||
Comment 5•5 years ago
|
||
:stas, :mossop - this is unlikely going to be the final review, but I'd like to get your big-picture feedback on the changes.
I expect that to improve our Web Components hooking interoperatibility and put us on a straight path to replace Localization.jsm with fluent-fallback
crate soon.
NI because I don't know how well phabricator works with r? on WIP patches.
Comment 6•5 years ago
|
||
I won't have time to look at this today and I'm away tomorrow. I'll try to have some feedback by the end of the week. Keeping the ni to make sure I won't forget.
Updated•5 years ago
|
Assignee | ||
Comment 7•5 years ago
|
||
Updated•5 years ago
|
Assignee | ||
Comment 8•5 years ago
|
||
Depends on D70971
Assignee | ||
Comment 9•5 years ago
|
||
Depends on D70972
Assignee | ||
Comment 10•5 years ago
|
||
Depends on D70973
Assignee | ||
Comment 11•5 years ago
|
||
Depends on D70974
Assignee | ||
Comment 12•5 years ago
|
||
Depends on D70975
Assignee | ||
Comment 13•5 years ago
|
||
Cleaned up the patches, maintaining the same approach as in comment 0.
Keeping the NI to get feedback, but I'm going to move forward and start asking for reviews in the next days.
Assignee | ||
Comment 14•5 years ago
|
||
Depends on D70976
Assignee | ||
Comment 15•5 years ago
|
||
Initially, I expected there to be a regression - we delay JSM loading on browser.xhtml for a reason, Unfortunately, it seems that this ship has sailed since with the increasing number of Web Components and dynamically injected DOM, we now have 6 elements that are not captured by the cache in browser.xhtml: [text-action-select-all, text-action-delete, text-action-paste, text-action-copy, text-action-cut, text-action-undo]
So, here I'm just cleaning up the order of initialization and helping Web Components integrate with less event loop trickery.
After this, I'll remove Localization.jsm and that should give us a speed win, unless L10nRegistry will continue to block (which it may to load the context for the 6 strings).
The path to perf win is now the elimination of JSMs, and/or improvements in the XUL Cache to cover Web Components.
Without that, we may end up removing the cache since at least for Fluent incomplete cache is not very useful at all.
Assignee | ||
Comment 16•5 years ago
|
||
I filed bug 1630743 to discuss the future of XUL Mini Cache.
If we want to keep the cache, I can bring it back while maintaining the cleanups I accumulated here.
The way I'd do this is by moving all state from Localization.jsm to Localization CPP and keep Localization.jsm as a stateless set of functions that just benefit from JS async iterator and cached iterator.
This would mean that we would load Localization.jsm
only the first time we need to call translateFragment
, translateElements
etc. so all state management, root connection, resource adding/removing, would be done on the class.
I think it's a good step toward JSM removal, so I'm going to work on that independently of the decision in the mini cache bug, but if we decided remove the mini cache, we could additionally clean up the whole Initial Translation
logic because it'd become just DOMLocalization::TranslateRoots
.
Assignee | ||
Comment 17•5 years ago
|
||
Comment 18•5 years ago
|
||
(In reply to Zibi Braniecki [:zbraniecki][:gandalf] from comment #0)
In particular, we currently have a gap in our coverage where if the document doesn't have l10n links, but web component injects one, it's possible that
document.l10n
won't be enabled until DOMContentLoaded.
What does it mean that document.l10n
isn't enabled? That it's not available? Or that its internal state prevents it from doing anything? If it's the latter, can you briefly explain why?
This means that right after initial link injection
document.l10n
becomes available, but onlyconnectRoot
,disconnectRoot
etc. can be called on it. Any attempt totranslateFragment
ortranslateElements
ortranslateRoots
will fail.
Would it make sense to make document.l10n
available and "live" always for all documents, similar to document.styleSheets
? Would it help solve the problem in this bug if document.l10n
could always be assumed to be "live", i.e. capable of translating the DOM? If there are no l10n links, it would localize as if all resources where missing. I'm assuming here that there are in fact very few caes where we don't want document.l10n
-- but it would be helpful to know what the actual data is.
Assignee | ||
Comment 19•5 years ago
•
|
||
What does it mean that document.l10n isn't enabled? That it's not available? Or that its internal state prevents it from doing anything? If it's the latter, can you briefly explain why?
enabled
means that document.l10n
is not null.
The issue is that if your Web Component gets connected while the document is parsing, and if there were no FTL links before, we fall into this branch: https://searchfox.org/mozilla-central/source/dom/base/Document.cpp#3873
And the result is that document.l10n
is not available in connectedCallback
, forcing users to do sth like:
connectedCallback() {
MozXULElement.insertFTLIfNeeded("browser/editBookmarkOverlay.ftl");
document.addEventListener("DOMContentLoaded", () => {
document.l10n.connectRoot(this.shadowRoot);
document.l10n.translateFragment(this.shadowRoot);
}
}
My goal is to enable document.l10n
as soon as the first FTL file is inserted and allow for root adding/removing right after:
connectedCallback() {
MozXULElement.insertFTLIfNeeded("browser/editBookmarkOverlay.ftl");
document.l10n.connectRoot(this.shadowRoot);
document.l10n.translateFragment(this.shadowRoot);
}
The issue with that translateFragment
requires (at the moment) the JSM file to be loaded. So to prevent JSM from having to be loaded (which is what we do to avoid perf hit on browser.xhtml), I'd have to root translate*
and format*
methods via a Promise that gets resolved when JSM gets loaded, and then it can be delegated to JSM's translate*
and format*
promises.
This is quite a bit of work, which I will do if we don't find a better solution.
Would it make sense to make document.l10n available and "live" always for all documents, similar to document.styleSheets?
No, that would mean that you need to initialize DocumentL10n, DOMLocalization, Localization and, at the moment Localization.jsm for all instances of Document
and we have hunderds of those on the startup path of the product.
L10n affects a small number of them and should be lazy.
We could make document.l10n
lazily initialize it, but the remaining question is if we need to load JSM as soon as the first interaction with DOMLocalization
happens.
We have at the moment two classes of operations on DOMLocalization
:
- Root and Resource management -
AddResourceId
,RemoveResourceId
,ConnectRoot
,DisconnectRoot
. - Translation and Formatting related -
FormatMessages
,TranslateElements
,TranslateFragments
,TranslateRoots
The former can be performed without loading of JSM since they're stored on the C++ side. The latter require the JSM to be loaded.
Further, for performance reasons, we need to kick-off fetching of I/O eagerly, so we can't just say "once the first TranslateFragment
gets called, initialize JSM" - we need to "initialize JSM and fetch I/O" as soon as possible and hope that by the time document is parsed the I/O is done.
So, if we want to allow people to add/remove roots and resources, but delay loading of JSM for (2), we need to be able to be in a state where you can call document.l10n.connectRoot
but you cannot call translateFragment
yet.
Alternatively, we can always load JSM, or we can remove JSM (I'm working on that), or we can reduce JSM to be stateless and then you could hook all methods of Localization
to be pending on JSM being initialized, and then they become available immediatelly, but resolve only after the JSM is loaded.
Assignee | ||
Comment 20•5 years ago
•
|
||
Since the conversation so far and further analysis leads me to believe that:
a) XUL Mini Cache is useful, even if it only reduces the amount of strings we localize at warm startup
b) JSM is loaded eagerly right now, and making it load lazily is actually more work than just skip the non-proto-elements
c) This patchset doesn't impact performance (see comment 15)
I suggest that we:
- Land these patches
- Re-introduce lazy-JSM in a new bug in which I'll move all state management to C++
- Work on Mini Cache for UA widgets in bug 1630743
- Work on not-caching if localization is incomplete in bug 1629291
Assignee | ||
Comment 21•5 years ago
|
||
This means that the value of this patchset is:
- We clean up initialization and progress toward de-JSMification
- We enable Web Components hooking during document parsing via
ConnectRoot(root, true)
For (2) in particular, since ConnectRoot
is easily greppable, if we ever decide to further change it, we can.
Assignee | ||
Comment 22•5 years ago
|
||
I filed bug 1631593 for the follow up after this lands.
Comment 23•5 years ago
|
||
(In reply to Zibi Braniecki [:zbraniecki][:gandalf] from comment #19)
The issue with that
translateFragment
requires (at the moment) the JSM file to be loaded. So to prevent JSM from having to be loaded (which is what we do to avoid perf hit on browser.xhtml), I'd have to roottranslate*
andformat*
methods via a Promise that gets resolved when JSM gets loaded, and then it can be delegated to JSM'stranslate*
andformat*
promises.This is quite a bit of work, which I will do if we don't find a better solution.
This comment made me understand the purpose of this bug, thanks.
Alternatively, we can always load JSM, or we can remove JSM (I'm working on that), or we can reduce JSM to be stateless and then you could hook all methods of
Localization
to be pending on JSM being initialized, and then they become available immediatelly, but resolve only after the JSM is loaded.
I was going to say that the stateless Localization.jsm
sounded like an interesting alternative, but I see that you already have a WIP in bug 1631593 :)
I think the approach here makes sense given the constraints and the goal of ultimately removing Localization.jsm
. My only comment about the API is that perhaps you could consider a separate method rather than an overload with a boolean argument, which hides the purpose a bit. Something like ConnectRootAndTranslate
or even TranslateRoot
. However, this is just a suggestion, I understand that the overload is temporary anyways. Overall, it looks good!
Assignee | ||
Comment 24•5 years ago
|
||
Comment 25•5 years ago
|
||
Comment 26•5 years ago
|
||
bugherder |
https://hg.mozilla.org/mozilla-central/rev/e8bf2e148410
https://hg.mozilla.org/mozilla-central/rev/fe7120dc7e90
https://hg.mozilla.org/mozilla-central/rev/4eeeb315a2bc
https://hg.mozilla.org/mozilla-central/rev/d129e3c9fe7f
https://hg.mozilla.org/mozilla-central/rev/01746241d8a2
https://hg.mozilla.org/mozilla-central/rev/1f62dc57e38e
https://hg.mozilla.org/mozilla-central/rev/aa4e68d8ad46
https://hg.mozilla.org/mozilla-central/rev/5262be825fdf
Description
•