The dom:loaded event is fired before defered scripts are executed

A good performance enhancer is to use the defer attribute on
scripts, which allows them to be downloaded in parallel with the
main document, and allow faster loading (display) of the content.
While prototype itself doesn't really support being defered, other
scripts can be. A good practice is to initialize scripts on
dom:loaded, which should trigger after all the scripts have been
downloaded, parsed and executed.

While the doScroll trick introduced by issue #127 does
trigger after the DOM has been completely loaded, it doesn't
respect the script execution condition, meaning that it fires while
most defered scripts are still in the loading phase, which means
that the dom:loaded event will be triggered before most scripts
have a chance to register themselves.

Is the doScroll check really helpful? What does it do better
than the readystatechange listener? I'm not aware of any major
advantages, but there's one really big disadvantage: it prevents
good performance by forcing scripts to be serialized.

Comments and changes to this ticket

The onreadystatechange will fire
complete just before window.onload fires
so that is no good.doScroll is useful for IE because it will throw errors
(assuming it's in the primary document and not an iframe)
when called until the DOM is ready (which means before images
are loaded and in this case before your deferred scripts).

The "DOMContentLoaded" event and its IE counterpart emulation
ONLY depends on the DOM being complete as per the HTML5
specifications describing it. No dependencies exists about
stylesheets being loaded/applied or scripts being loaded/executed
or any other external binary requirement being available
(img/object/applet etc.).

So if you use the "defer" attribute to load and execute script
asynchronously you should also setup notification handlers for when
they are ready so you know when to start using the loaded
scripts.

Also remember that you can setup scripts to load asynchronously
using different techniques, "defer" is just one of them, but it is
not evenly available on all browsers and where it is available
there is nothing ensuring it will behave the same across different
platform/browsers.

The "doScroll()" fires before the onreadystatechange event is in
the "complete" state and depending on the number of external binary
files (stylesheets/scripts/images) it can fire several seconds
before the "onreadystatechange" event. The complete
"IEContentLoaded" solution is based on a combination of the
doScroll() trick and the onreadystatechange event. It is not
possible to solve all the cases with only the "doScroll()".

Basically in IE the "doScroll()" is used when the page is not
cached, while the "onreadystatechange" event is used when the page
is already cached to ensure the notification always happens before
the "onload" event. This is especially problematic when using the
Back/Forward browser buttons to navigate through pages that are
already cached.

I fully understand the problem you are exposing, however that's
not due to the "doScroll()" method, use asynchronous XHR requests
to load your external scripts. You can do that as soon as
javascript executes, no need to wait for ready/load notifications
to do that.

It is not the "doScroll()" blocking the load process, it is the
exact contrary, the script execution is what is blocking/deferring
the notifications. Both "dom:loaded" and "load".

"The onreadystatechange will fire complete just before
window.onload fires so that is no good."

Correct ... it is not as good as the "doScroll()" for a primed
cache hit.

But as I explained above this does not cover when pages and
external binaries are already cached, that's why I also use
"onreadystatechange" as a fallback to catch those cases.

In those cases, the risk of only using "doScroll()" is that it
could fire after the "load" event (just few milliseconds) but being
late in notifying that, can cause different unexpected behaviors in
user scripts.

The reasons "doScroll()" could fail in those cases is due to the
fact the check is TIME driven by "setTimeout()" (25/50 msec) not
EVENT driven by a system event.

The "DOMContentLoaded" event and its IE counterpart emulation
ONLY depends on the DOM being complete as per the HTML5
specifications describing it. No dependencies exists about
stylesheets being loaded/applied or scripts being loaded/executed
or any other external binary requirement being available
(img/object/applet etc.)

It is not the "doScroll()" blocking the load process, it is the
exact contrary, the script execution is what is blocking/deferring
the notifications. Both "dom:loaded" and "load".

Yes, I know, I didn't mean it as "doScroll physically causes the
browser to abort parallel download", but as "not being able to
consistently receive the dom:loaded event causes developers not to
use defer or other parallel download tricks".

The HTML5 draft spec also says it has to be flagged
parser-inserted. I cannot determine if that means it
has to be inserted via
insertBefore()/appendChild() or some
other mechanism. Also keep in mind this is draft spec and
browsers may not follow it especially IE.

I would agree wtih Sergiu that "parser-inserted" seems to imply
that the parser found the script-tag while parsing the source and
inserted it into the DOM. I wonder if there's a complementary
"script-inserted" flag that can/should exist to indicate scripts
which are dynamically added to the DOM via script logic.

In any case, I've long heard people assert that "defer=true"
(and now async=true) are supposed to emulate the same behavior as
using a script-loader to insert scripts dynamically. The behavior
I'm referring to is that it unpins the loading/executing of the
script from the rest of the page's behavior (namely, loading). I
think in practice this is quite true, in that a script that is
defer'd is not guaranteed to run before the dom-ready event has
fired (though certainly before onload fires).

Yes, the spec (for HTML5) indicates that defer=yes
scripts should cause the delay of dom-ready until they
finish. But that's a draft spec for an as-of-yet-incomplete HTML5
implementation. Far more practical is the pragmatic observation of
how the browsers CURRENTLY do things.

As for the doScroll() IE hack, according to IE internal
documentation, it shouldn't make doScroll available until a very
similar point in processing to what we'd call dom-ready. That's far
from saying it's actually dom-ready. It's also far from saying that
IE would have chosen to delay doScroll() intentionally to wait for
defer scripts. In fact, again as practically observed, they do
not.

On the other hand, script loaders like my LABjs project have been able to reliably
handle detecting when scripts finish, which is a far better "event"
to wait on that some emulated/hacked dom-ready. If you're searching
for better performance, which it sounds like you are, it would be
much better to implement using a script loader like LABjs and
execute initialization code when the scripts finish, regardless of
what the rest of the page is doing.

ONE NOTE: Prototype AFAIK is known to do what I call "unsafe"
dom-ready detection in that, in FF, etc (not IE), it doesn't check
for the presence of document.readyState already being set.

To my knowledge, jQuery 1.4+ was the first (and only since)
library that checked for document.readyState at run-time of the
library, so that if the library was being loaded dynamically and
the dom-ready even had happened to already be passed, then it would
immediately set the internal "ready" flag to true and let any
dom-ready waiting code fire immediately. For jQuery, this means
that it can finally be loading dynamically (like by a script
loader) without any fear of interferring with
$(document).ready(...) blocks.

However, as far as I know, Prototype cannot (yet) be reliably
inserted dynamically into a document because it will not properly
check for and recognize if dom-ready has already passed. So, my
advice remains to be: 1) load prototype manually with a script tag
(no defer!) 2) load everything else with LABjs.

@Tobie- That's essentially how jQuery does it. The difference of
course is the check on "document.loaded", which they check
"document.readyState".

I understand that it makes dom-ready a special type of event in
that it immediately fires if already passed. But think about it...
dom-ready is quite special... it only ever fires once. And there's
lots of important code that waits on it.

Moreover, we need a pattern like this (wait, or immediately
execute if already fired) so that authors can write code that is
robust enough to work no matter how their code is added to a page.
Imagine this: I write a little plugin that pops up a cool little
picture on a page when you hit the K key. Now, that plugin
obviously needs to "wait" for dom-ready, as it's going to modify
the DOM.

But if you consider the plugin AND its library (prototype,
jquery, etc) as a self-contained package (or an individual resource
with dependencies, however you wanna look at it), that entire
"package" needs to be present on the page.

But, I as the plugin author have no idea how people will include
it on pages. Someone may load it in via a blocking script tag.
Someone else may dynamically load it with a script loader (like
LABjs). And still someone else may dynamically add it to a page
with a bookmarklet or other browser extension of some sort.

In all cases, my code needs to say "hey, i just need to make
sure dom-ready has passed before i do my thing. I don't care if it
happened a long time ago, or hasn't happened yet. Just queue me up
to execute for when it does, or now, whichever is later".

The framework then needs to be able to detect if it itself was
added to a page after the dom-ready has already passed. If so, it
can safely signal to anyone later "yup, the dom is ready, go for
it".

And the only sensible way for me to hook my code into happening
either now (dom-ready already passed) OR later (dom-ready happening
in the future) is to attach a listener for the special "dom-ready"
event, and assume the framework will do the execute-now-or-later
magic for me.

Otherwise, my plugin would have to be written custom for each
usage pattern. This is not good.

@Tobie- I should also say that I consider any dom-ready
detection as "unsafe" if it uses any techniques which are not
"safe" for the dynamic inclusion (script loader) use case. Not
checking for document.readyState is one such problem many
frameworks have. But another problem is use of things like
document.write().

It's fine if a framework wants to intentionally not support the
idea of it being dynamically added to a page sometime well after
page-load. But if it uses these techniques on purpose and ignores
this increasingly popular/important use case of dynamic/on-demand
loading, I call that "unsafe" dom-ready behavior.