Closed Bug 1000806 Opened 10 years ago Closed 10 years ago

Provide a way for mozL10n.{once,ready} to invoke callback when DOM is interactive

Categories

(Firefox OS Graveyard :: Gaia::L10n, defect, P2)

defect

Tracking

(Not tracked)

RESOLVED INVALID

People

(Reporter: stas, Assigned: zbraniecki)

References

Details

The 'localized' event describes the state of the content, not the viewport, and as such it should be emitted by document, similar to DOMContentLoaded.
I suggest document.onlocalized. In L20n we use 'DocumentLocalized' but since the event is fired on document element, I find the "Document" part redundant.

I also suggest changing it in L20n if we agree to use it here.
I generally agree, except that in l20n, DocumentLocalized is fired only once, when the context is first ready.  Subsequent language changes don't cause the event to be emitted, just document.l10n.onready. Maybe we should rethink how this works in L20n, especially when window.onlanguagechange (bug 889335) becomes a thing.
(In reply to Staś Małolepszy :stas from comment #2)
> I generally agree, except that in l20n, DocumentLocalized is fired only
> once, when the context is first ready.  Subsequent language changes don't
> cause the event to be emitted, just document.l10n.onready. Maybe we should
> rethink how this works in L20n, especially when window.onlanguagechange (bug
> 889335) becomes a thing.

Awww... you're right.

I believe we should rethink it, but not with onlanguagechange - this is an event fired when language changes, not when document is retranslated with new resources.

My first reaction is:

document.onl10nload (or document.l10n.onload) - for the first time the document is fully localized
document.onlocalized - on each localization completed
After more thinking, I disagree with myself.

My thinking goes like this:

document.l10n (or navigator.mozL10n) is a localization context. It should fire an event for when the resources are available (and refire after language change).

document should fire when document's content has been retranslated into a new locale.

Those are two *reoccurring* events.

Because how often apps should wait before interacting with the DOM until said DOM has been translated into it's first language, it makes sense to have a third event, fired when the document is translated for the first time. I believe that this event should be fired on the document itself, not l10n context.

Three events - two on document, one on l10n context. That's all.

I would not bind any of them to onlanguagechange because there are other ways to switch context's locale (manual settings) than reacting to change to navigator.languages' pool.
See bug 1000895 - it assumes that window.onlocalized means that both DOM and L10n are ready.
Blocks: 999779
Blocks: 1006359
Assignee: nobody → gandalf
Priority: -- → P2
Summary: The localized event should be fired on document, not window → Clean up l10n events and bootstrap wrappers
Loose notes from last week IRL conversations:

We operate in the space with three relevant events:

 1.1) document.onDOMContentLoaded - fired by the browser when the DOM becomes interactive;

 1.2) l10n.onReady - fired each time language is changed and localization resources have been loaded, parsed and compiled;

The above two events are racy.  The order in which they fire is unknow at runtime.

 1.3) document.onDOMLocalized - fired each time when the DOM bindings have finished localizing all DOM nodes;  this events always fires *after* the two above have.

It's possible to register handlers for all of these events via the addEventListener API.  It is however problematic for 1.2 and 1.3 because by the time a developer is able to register a handler the event might have already fired.

(DOMContentLoaded works around this by exposing the state of the DOM in document.readyState and by allowing scripts to be deferred until just before DOMContentLoaded is fired (bug 688580).)


* * *

Due to the above limitation of the addEventListener API, the following wrapper methods could be (or already are) provided:

 2.1) l10n.ready(callback) - the callback is always registered as a l10n.onReady listener (for the future lang changes) and is also fired now if the l10n resources have already been loaded

 2.2) l10n.once(callback) - the callback is invoked only once, either now if the l10n resources have been loaded, or as soon as they will complete loading.

(Aside:  the names ready and once are the legacy of the previous l10n library;  we're considering finding new names that reflect the behavior better.)

 2.3) document.whenDOMLocalized(callback) - the name of the wrapper is TBD (DOMContentLocalized?), as is the object on which it should be called (document? l10n?); the callback fires each time when the l10n DOM bindings have finished localizing the current DOM into the current language.


* * *

Note that the following code snippets are different:

  3.1) l10n.ready(callback);

    Use it when the callback doesn't need to operate on the DOM, but needs the l10n resources to be loaded.

  3.2) document.addEventListener('DOMContentLoaded', l10n.ready.bind(l10n, callback));

    Use it when the callback *does* need to operate on the DOM and it also needs the l10n resources to be loaded.

  3.3) document.whenDOMLocalized(callback)

    Use it when the callback depends on the DOM being already *localized*.  This also implies that the DOM is at least in the interactive state and the l10n resources have been loaded.


* * *

l10n.once is particularly well suited for initialization.  In majority of scenarios developers should either use variations of 3.1 or 3.2 with l10n.once.  It might be tempting to have a version of 3.3 which unregisters the handler, but it is important to understand the difference in semantics between 3.2 and 3.3:  3.2 doesn't wait for the additional task of localizing all DOM nodes.

For rare cases when you do need to run some logic only once when the DOM node are localized, a suggested solution would be something like this (requires the implementation of whenDOMLocalized to use setTimeout(callback), or at least to internally first register the handler and only then invoke the callback if the current state of DOM nodes lcoalization allows it)

 document.whenDOMLocalized(function onDOMLocalized() {
   document.removeEventListener(onDOMLocalized);
   // the rest of your code here…
 })
See Also: → gaia-perf-events
Blocks: 1000860
Blocks: 1000852
Blocks: 1003191
Blocks: 1000130
Blocks: 1000895
Blocks: 1002625
Blocks: 999195
Blocks: 996272
I've just had an epiphany.

(In reply to Staś Małolepszy :stas from comment #6)

> (DOMContentLoaded works around this by exposing the state of the DOM in
> document.readyState and by allowing scripts to be deferred until just before
> DOMContentLoaded is fired (bug 688580).)

>   3.2) document.addEventListener('DOMContentLoaded', l10n.ready.bind(l10n,
> callback));
> 
>     Use it when the callback *does* need to operate on the DOM and it also
> needs the l10n resources to be loaded.

In fact, you don't need to explicitly listen to DOMContentLoaded events as long as the script in which you want to call l10n.{ready,once} is deferred.  When deferred scripts are run, document.readyState is already 'interactive', and you can safely query the DOM, even if DOMContentLoaded hasn't fired just yet.  See:

http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-end

This mitigates the risk of l10n.{ready,once} invoking its callback too early.  Gandalf, do I understand this correctly?

NB.  Some apps (e.g. bug 1000860) choose to wait for window.onload instead of DOMContentLoaded.  In these cases we should preserve the onload handler around l10n.once:

  window.onload = function() {
    l10n.once(init);
  };

Case 3.3 remains unsolved and I'm still thinking about it.
Flags: needinfo?(gandalf)
One more comment here - with bug 996038 done, we will ask developers to manually decide when they will emit "chrome-visible" (or "chrome-ready") event. If the app has any strings in chrome we will want them to emit it *after* DOMContentLocalized, and it should happen only once per app launch (no retanslation).

Which means, that in the simplest case (when all chrome is in HTML and when the app uses mozL10n) they will have a deferred script that will do:

 document.whenDOMLocalized(function onDOMLocalized() {
  document.removeEventListener('DOMLocalized', onDOMLocalized);

  window.dispatchEvent(new CustomEvent("chrome-ready"));
 });

For that reason it sounds like we may want to optimize it somehow by either:

1) allowing l10n to capture "chrome-ready" bubbling, wait for DOMLocalized and retrigger it up to the system.

or

2) writing a solid wrapper that makes this code pattern easy.

Eli, what do you think?
Flags: needinfo?(gandalf) → needinfo?(eperelman)
In my opinion, I would vote for the second option if it reduces the burden on app owners. In the second case, do you believe it would still consume "chrome-ready", or would you leave that implementation piece to app-land code?
Flags: needinfo?(eperelman)
I would leave it to the App. I would imagine sth like:

document.whenFirstDOML10nReady(function() {
  window.dispatchEvent(new CustomEvent("chrome-ready"));
});

I'm open to discuss the name of the wrapper :)
I have a WIP here: https://github.com/stasm/gaia/tree/1000806-events.  I'll test a bit more and clean it up and will attach a patch for wider feedback.
One thought. In 'pretranslated' case - shouldn't we be able to fire MozDOMLocalized before mozL10n context is loaded?

Asking because in perf events it seems that we want to fire 'moz-chrome-dom-loaded' *after* DOM localization is done which in pretranslate case is before mozL10n is ready and in non-pretranslated case after.
(In reply to Staś Małolepszy :stas from comment #11)
> I have a WIP here: https://github.com/stasm/gaia/tree/1000806-events.  I'll
> test a bit more and clean it up and will attach a patch for wider feedback.

https://travis-ci.org/mozilla-b2g/gaia/builds/25937130
https://tbpl.mozilla.org/?tree=Gaia-Try&rev=bdef262f3076
As per comment 7, I'm morphing this into a bug about controlling mozL10n.{ready,once} in regard to document's readyState.

I'll file new bugs for the remaining cases discussed in this bug.
Summary: Clean up l10n events and bootstrap wrappers → Provide a way for mozL10n.{once,ready} to invoke callback when DOM is interactive
Blocks: 1020247
Blocks: 1020249
(In reply to Staś Małolepszy :stas from comment #6)
 
>  1.3) document.onDOMLocalized - fired each time when the DOM bindings have
> finished localizing all DOM nodes;  this events always fires *after* the two
> above have.

Bug 1020243. The proposed semantics are slightly different.  mozDOMLocalized would not guarantee the readiness of mozL10n.

>  2.3) document.whenDOMLocalized(callback) - the name of the wrapper is TBD
> (DOMContentLocalized?), as is the object on which it should be called
> (document? l10n?); the callback fires each time when the l10n DOM bindings
> have finished localizing the current DOM into the current language.esources have been loaded.

Bug 1020249.

Related follow-ups: bug 1020247 and bug 1020250.
Resolving as invalid, because there already is a way to control whether or not the callback to mozL10n.{ready,once} will have access to an interactive DOM.  See comment 7:
 
(In reply to Staś Małolepszy :stas from comment #7)

> In fact, you don't need to explicitly listen to DOMContentLoaded events as
> long as the script in which you want to call l10n.{ready,once} is deferred. 
> When deferred scripts are run, document.readyState is already 'interactive',
> and you can safely query the DOM, even if DOMContentLoaded hasn't fired just
> yet.  See:
> 
> http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-
> end
> 
> This mitigates the risk of l10n.{ready,once} invoking its callback too
> early.
Status: NEW → RESOLVED
Closed: 10 years ago
Resolution: --- → INVALID
You need to log in before you can comment on or make changes to this bug.