This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 13965 - Exposing onreadystatechange on script elements seems to not be web-compatible unless they fire the event
Summary: Exposing onreadystatechange on script elements seems to not be web-compatible...
Status: RESOLVED FIXED
Alias: None
Product: HTML WG
Classification: Unclassified
Component: HTML5 spec (show other bugs)
Version: unspecified
Hardware: Other other
: P3 blocker
Target Milestone: ---
Assignee: Ian 'Hixie' Hickson
QA Contact: HTML WG Bugzilla archive list
URL: http://www.whatwg.org/specs/web-apps/...
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-08-29 21:38 UTC by contributor
Modified: 2011-09-09 22:49 UTC (History)
10 users (show)

See Also:


Attachments

Description contributor 2011-08-29 21:38:54 UTC
Specification: http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html
Multipage: http://www.whatwg.org/C#elements-in-the-dom
Complete: http://www.whatwg.org/c#elements-in-the-dom

Comment:
Exposing onreadystatechange on script elements seems to not be web-compatible
unless they fire the event

Posted from: 71.184.125.56
User agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0a1) Gecko/20110822 Firefox/9.0a1
Comment 1 Boris Zbarsky 2011-08-29 21:42:19 UTC
See https://bugzilla.mozilla.org/show_bug.cgi?id=682554#c4 for some background.

In short, yandex is using |element.onreadystatechange == null| to detect support for firing readystatechange events on <script> elements.  Presto and Trident fire those events.  WebKit and Gecko do not, and used to have .onreadystatechange test undefined on script elements, until Gecko tried implementing this part of the spec.

The options here in terms of web compat are to either only have on* properties exist on elements they make sense on (which I would somewhat prefer, since it would simplify capability detection of the sort yandex was trying for) or to spec and implement readyState and readystatechange events on <script> elements.  Or both, since I doubt Presto and Trident will be removing their readyState-on-script stuff, and since I do think that only exposing events on the elements they make sense for improves the capability-detection situation.
Comment 2 Boris Zbarsky 2011-08-30 03:16:56 UTC
Note that per http://msdn.microsoft.com/en-us/library/ms536957%28v=vs.85%29.aspx lots of other elements fire readystatechange events in IE.
Comment 3 Henri Sivonen 2011-08-30 11:20:17 UTC
I think we should support readystatechange events for scripts in an IE-compatible way. Kyle had a use case for that feature.
Comment 4 Boris Zbarsky 2011-08-30 13:56:31 UTC
Note that Opera's implementation is not particularly IE-compatible, though that's caused them web compat problems, and that IE's is not very internally consitent either....
Comment 5 Kyle Simpson 2011-08-30 14:22:27 UTC
> I think we should support readystatechange events for scripts in an
IE-compatible way.

I very much support this being done. There's a proposal to WHATWG to do exactly that.

However, in the interest of making sure we're all on the same page, there's a very important nuance there that you may or may not be aware of/intending, which in its ommission would be quite a bad thing.

To say "let's support `readystatechange` like IE does" would also need to mean that you support IE's current behavior of "preloading" a script as soon as the `src` attribute is set on an element, but not executing the script until that element has been appended to a DOM.

Why? Because part of what IE does with the "readystatechange" event is to start off `readyState` at value "uninitialized". Then when the script finishes *loading* only (NOT when it finishes executing, which is the `onload` event), it fires for `readyState==loaded` (or "complete" depending on if it was from cache or not). If the script element is not attached to a DOM at that point, this event fires to let you know the script finished loading, but didn't execute.

That behavior is precisely the behavior relied upon in LABjs in IE for "preloading", because it allows many scripts to be loaded in parallel, but their execution order be controlled by what order they are added to the DOM, once they are finished loading.

The proposal I put forth to WHATWG was to standardize this behavior from IE:

http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_2_.28Kyle_Simpson.29

In fact, the spec already mentions the "preloading" behavior I described as a performance suggestion. The spec does not currently mention the `onreadystatechange` event behavior, which is CRITICAL to making a preloading mechanism work, but that's exactly what my proposal says should happen.

-------------

To be clear, if any browser implements `onreadystatechange` and `readyState` on script elements, AND they start it out initially at value "uninitialized" as IE does, but they don't do the above "preloading" behavior, then that WILL break current versions of LABjs (2.0+), because LABjs tests for this and uses it (obviously currently only in IE).

The reason it's not broken in Opera is because Opera doesn't start out the value at "uninitialized" like IE does. So, the feature-test in LABjs looks not only for the `readyState` property on a script element, but also for its value being "uninitialized". I've had several Opera devs promise that they would never change there default value to "uninitialized" unless they were also copying the "preloading" behavior, so at the moment, the feature-test as described is safe.

Bottom line: I fully support, and have proposed, that script elements be standardized to behave like they do in IE with regards to `readyState`. But that also critically means that they behave fully (not just partially) the same, meaning that they do the useful "preloading" behavior too. Going half-way on that would very much break LABjs, and thus any of the many sites using it.
Comment 6 Boris Zbarsky 2011-09-03 05:34:34 UTC
Sounds to me like if we ship this in Gecko we would be shipping the Opera behavior that always reports "loaded" for the readyState.

I hate the web, with it's broken sniffing mess....
Comment 7 Ian 'Hixie' Hickson 2011-09-03 05:41:56 UTC
Would having <script> elements have a readyState IDL attribute that has the following states, and for which an event gets queued whenever the value changes, be ok?

 'uninitialized' - script file isn't loaded (or specified) yet, but UA is going to preload it ASAP (after the "src" is set if it's not already set).

 'loaded' - script file is loaded, or, script file isn't loaded but UA isn't going to preload it anyway so it doesn't matter.

 'complete' - script file is loaded and has executed. (I'll check with IE about whether this change happens before or after onload, and whether the event gets queued before or after onload.)

I believe this would allow both for the IE preload behaviour and for the non-preload behaviour, and would be compatible with yandex and LABjs. (We want to allow preload to be optional since it should, in theory, be just a performance issue, and some UAs may prefer to err on the side of low bandwidth usage rather than on the side of fast script execution.)
Comment 8 Boris Zbarsky 2011-09-03 05:48:29 UTC
I think that would work ok with a few caveats:

1)  We would need an onreadystatechange event right after onload (at least)
    without the readyState actually changing.
2)  I've never seen IE go into "complete" for external scripts in my testing;
    just for inline scripts (which never go into loaded).
3)  Need to define this stuff for inline scripts in general, etc.
Comment 9 Ian 'Hixie' Hickson 2011-09-03 05:53:18 UTC
If people are ok with this, I'll investigate more closely about what exactly it needs to be when I spec it, hopefully early next week.

Modulo compatibility constraints, I'd say all scripts start in 'uninit'/'loaded' and transition to 'complete' at the same time as 'load', whether inline or external. It's probably not quite what IE does, even modulo the preloading stuff, but it seems saner and still compatible with what has been described so far.
Comment 10 Kyle Simpson 2011-09-03 12:58:13 UTC
(In reply to comment #7)

> Would having <script> elements have a readyState IDL attribute that has the
> following states, and for which an event gets queued whenever the value
> changes, be ok?
> 
>  'uninitialized' - script file isn't loaded (or specified) yet, but UA is going
> to preload it ASAP (after the "src" is set if it's not already set).

Does this mean that a UA like Firefox (which seems unwilling to do preloading) would *not* start in "uninitialized"?

Let me reiterate from my previous comments... if you have a new freshly created dynamic script element with a readyState==="uninitialized" (like IE does), but that UA is *not* going to do preloading, then that WILL break LABjs badly.

In other words, a UA that doesn't do preloading must specifically be forbidden to start out with this value.


>  'loaded' - script file is loaded, or, script file isn't loaded but UA isn't
> going to preload it anyway so it doesn't matter.

This would only be acceptable for LABjs (that is, not break it) if UA's that do not support "preloading" never have the previous "uninitialized" value. I think that's what you're implying here, but I just want to clarify that's in fact a true statement.

LABjs never tries to use `readyState` preloading if the initial value is not "uninitialized"... so that's the locked door entrance for this behavior, that needs to either unlock if there's preloading, or stay locked if there's not. If that's true, then any other progression of the values is irrelevant to LABjs because it won't inspect or use them.


> I believe this would allow both for the IE preload behaviour and for the
> non-preload behaviour, and would be compatible with yandex and LABjs. 

With the clarification of "uninitialized" above, it won't break LABjs.


-----------------------------
> (We want
> to allow preload to be optional since it should, in theory, be just a
> performance issue, and some UAs may prefer to err on the side of low bandwidth
> usage rather than on the side of fast script execution.)

We've had lengthy discussions about active intentional preloading, and there are literally more than a dozen arguments that I've provided as use-cases for why preloading is necessary for the web. I strongly object to the implication that it's a side optional issue that should be left unspec'd.

Would it help if I collected "signatures" on a petition to show you how much value and interest there is in some sort of official "preloading" mechanism (sans hacks)?

If your only concern is that some UA's (in low-bandwidth scenarios) would *potentially* (and I stress potentially, because they might not) load scripts too early if they start fetching on setting of the `src` property (in other words, how IE has done it since v4), then at least consider the first proposal from Nicholas Zakas, which makes preloading an *explicit* thing rather than an implicit thing.

Surely you can see that if an author (such as me, of LABjs) decides to opt for *explicit* preloading, then that author is doing it on purpose, with a specific intention to use the scripts in a specific way (not an accident of implicit preloading), and he has thus said that script loading is the most important thing to enable/preserve. He's furthermore saying, by opting into explicit preloading, that the UA should in fact load-but-not-execute the scripts in question, because the author of the script loader intends to decide (later) in what order they execute.

Explicit preloading under Zakas' proposal would be a fair balance between the fears of bandwidth-wasting in implicit preloading (my proposal) and no direct preloading at all, which continues to plague the land of script loaders with all manner of horrible hackiness, like the awful "cache preloading" that is so brittle and inefficient.
Comment 11 Ian 'Hixie' Hickson 2011-09-03 18:58:49 UTC
Yes, I'm proposing that UAs that don't do preloading will start in the state "loaded".

Signatures won't help. The spec is going to spec what browsers are willing to implement, regardless of how many or few people (including myself) think what they implement is a good idea.
Comment 12 Kyle Simpson 2011-09-04 04:21:07 UTC
(In reply to comment #8)

> 1)  We would need an onreadystatechange event right after onload (at least)
>     without the readyState actually changing.

That's incompatible with how IE does it. IE9+ fires both `onload` and `onreadystatechange`, but it fires `onreadystatechange` for "loaded" (or "complete") *before* it fires `onload`.

http://test.getify.com/test-script-onload-and-readystate.html

Try that test in IE9 (clear your cache -- *not* just refresh or shift+f5 -- each time you run it) to verify the order of events.


> 2)  I've never seen IE go into "complete" for external scripts in my testing;
>     just for inline scripts (which never go into loaded).

Incorrect. IE fires "complete" instead of "loaded" when the script is purely pulled from cache.

Again, try out this test, but do so with your cache intact from a previous run of the page, and you'll see that IE never fires "loading" or "loaded", it only fires "complete". But it does so strictly before it fires "onload".

---------------------
(In reply to comment #9)

> Modulo compatibility constraints, I'd say all scripts start in
'uninit'/'loaded' and transition to 'complete' at the same time as 'load',
whether inline or external. It's probably not quite what IE does, even modulo
the preloading stuff, but it seems saner and still compatible with what has
been described so far.

There are definitely scripts on the web which rely on IE's behavior of "complete" for scripts pulled from cache, and "loaded" for scripts that were actually loaded.

IE fires one or the other ("loaded" or "complete"), but not both. Firing only "complete" in all cases will mess up scripts which are looking for "loaded" only.

And what does "at the same time" actually mean? The order of when they fire (as shown above) is not clear, and is quite important.

I would strongly caution against spec'ing an incompatible (with IE) set of behavior around `readyState` on script elements, because they've been around doing this forever, and there's tons of potential for compat breakage. I would wager a lot of money that IE will be reluctant to bring their implementation into alignment with a spec for a different set of behavior or ordering of events in this area, because of that potential for breakage (lots of sites still have IE-specific code).

It's a bad idea to spec something and say "Well, IE be damned, we're doing it anyway", unless there's an overwhelming reason to do so. Saying something is "saner" is nowhere near that level of overwhelming.
Comment 13 Boris Zbarsky 2011-09-04 14:26:08 UTC
> but it fires `onreadystatechange` for "loaded" (or "complete") *before* it
> fires `onload`.

Interesting.  I tested precisely that in IE9, and I saw the onload firing before the onreadystatechange...  Quite curious.

> IE fires "complete" instead of "loaded" when the script is purely pulled from
> cache.

That's insane.  Par for the course, I guess, but insane.  I don't see a need to duplicate this behavior, honestly.  For one thing, a UA may have no idea whether the script came from cache or not (think intermediate caches, for one thing!).

I fail to see how scripts could possibly depend on whether another script "came from cache" (whatever that even means, given intermediate caches).

I also fail to see why implementing something like what Opera did would not be compatible with the web.
Comment 14 Kyle Simpson 2011-09-04 14:54:40 UTC
(In reply to comment #13)
> > but it fires `onreadystatechange` for "loaded" (or "complete") *before* it
> > fires `onload`.
> 
> Interesting.  I tested precisely that in IE9, and I saw the onload firing
> before the onreadystatechange...  Quite curious.

Can you re-run your tests in your IE's with the URL I provided? Which order is it in? I haven't seen any other order than what I described, but I suppose it's possible that various race conditions control that.


> > IE fires "complete" instead of "loaded" when the script is purely pulled from
> > cache.
> 
> That's insane.  Par for the course, I guess, but insane.  I don't see a need to
> duplicate this behavior, honestly.  

I'm not entirely sure that you need to, either -- I was just pointing out that IE's implementation is more complex than was being described, and so should be considered carefully in any attempt to partially-emulate. But more on that in a moment.


> For one thing, a UA may have no idea
> whether the script came from cache or not (think intermediate caches, for one
> thing!).
> 
> I fail to see how scripts could possibly depend on whether another script "came
> from cache" (whatever that even means, given intermediate caches).

I have seen code written specifically for IE which loaded a script URL, and if it seemed to come quickly from cache (both timing wise and this "complete" state), then a loader wasn't needed, but if not, then a loader icon was displayed.


> I also fail to see why implementing something like what Opera did would not be
> compatible with the web.

Again, I'm not specifically saying that "like what Opera did" would be wrong to copy. However, it's more complicated than you say.

Opera basically has a rather non-functional `readyState` implementation on their script elements. If you run the test in Opera 11.5, you'll see that they start out the `readyState` variable with "loaded" (*not* firing `onreadystatechange`) on a new script element, then they fire `onload` (different order from IE!), then they finally fire `onreadystatechange`, but with the same value.

*That* seems "insane" to me. They fire `onreadystatechange` when the `readyState` did not, in fact, strictly change. Why would you want to actually spec something as insane and counter-intuitive as that?

This is basically a `readyState` system in name-only. There's no progression of values, so the concept of a "change" event is bogus. And they artificially fire the event, even without a change, for some unknown reason.

Remember, Opera supports `onload`, so I fail to see any reason at all why Opera has this non-functional and confusing `readyState` system that works quite differently from IE's (at least partial attempt at) full `readyState` value progression and change events.

Moreover, I've had a number of discussions with a few Opera developers, and the impression I got was that they eventually were considering "fixing" their `readyState` system so that it behaved more "correctly". Specifically, there was at least some consideration on their part to do the "preloading" like IE does, with a full progression of `readyState` values, per my proposal.

Given that Opera's implementation is bogus, and that their contemplating fixing/replacing it, why on earth would we consider spec'ing that?

--------

My counter-proposal for the spec:

If a UA is going to define and support `readyState` and `onreadystatechange`, it must at a minimum have one state change, from "unloaded" (or "uninitialized") to "loaded".

If not, the UA may define a `readyState` variable, but its default value must be "unsupported", so that developers can properly detect (by inspecting the default value on a newly created script element) if the mechanism is supported or not.

The feature test would then be:

var script = document.createElement("script");
var readyStateSupported = script.readyState && (script.readyState == "unloaded" || script.readyState == "uninitialized");

For IE, this would pass. For Opera's current implementation, it would not pass. For any UA's (perhaps Firefox?) newly wishing to have `readyState` "in name only but not functionality", it would also not pass. For any UA's that intend to create a working/sane `readyState` mechanism, they do the "unloaded" -> "loaded" change, firing the event once, and the above test would pass.

That allows a UA like Firefox to either have the property but not use it, OR have the property and actually use it in a sane way, but in neither case does that UA *have* to do the preloading that IE does.

This is a compat win.
Comment 15 Boris Zbarsky 2011-09-05 03:29:33 UTC
Per comment 1, any UA which has an "onreadystatechange" property on <script> must also at some point fire a "readystatechange" event at a point when .readyState returns either "loaded" or "complete".  Not doing that breaks at least Yandex.

It's not clear to me that your last proposal has this property.
Comment 16 Kyle Simpson 2011-09-05 12:51:26 UTC
(In reply to comment #15)
> Per comment 1, any UA which has an "onreadystatechange" property on <script>
> must also at some point fire a "readystatechange" event at a point when
> .readyState returns either "loaded" or "complete".  Not doing that breaks at
> least Yandex.
> 
> It's not clear to me that your last proposal has this property.

OK, fair enough. I ammend my proposal to exclude the "unsupported" case.

To restate, then, I propose that `readyState` start out at "unloaded", and at a minimum move from "unloaded" to "loaded" with a single `onreadystatechange` firing. This firing should happen *before* `onload`. 

Why? Because IE fires "loaded" (or "complete") even if it has not executed the script (only downloaded it, for preloading), whereas `onload` always comes strictly after the script is executed.

Additionally, since IE has always started out at "uninitialized", we should allow that initial value as well, so `readyState` can either start out as "uninitialized" or "unloaded".

To prevent breakage of LABjs, if the initial value is "uninitialized" (like IE), then the UA must support preloading (like IE). If the initial value is "unloaded", the UA must not support preloading.

This lets UA's choose to do a full `readyState` progression mechanism, which implies preloading (like IE), or just a simpler `readyState` mechanism which only fires once, from "unloaded" -> "loaded".

My proposal would strictly make Opera's current implementation non-conforming, but I think that's a good thing because I think it's quite non-sane/confusing how they artificially fire the event even if the value doesn't actually change. I think they'd probably admit it's non-ideal as well.

The saving grace is, it's non-conforming for Opera in non-breaking way, in that current/past Opera's will continue to artificially fire once for "loaded", and them later changing their initial value to "unloaded" to achieve spec conformance shouldn't be a breaking change for them or any web content.

-------------------

Yandex's current feature test seems to just be if `readyState` property is present. IMHO, this is a poor feature test, and should be changed. However, even if they don't change, my proposal should preserve their functioning.

For IE prior to v9, they didn't support `onload`, so `onreadystatechange` was the only option. It makes complete sense why scripts would rely on it in IE.

This was never true for Opera though (at least not in recallable memory). They always had the (seemingly) more preferable `onload` support. So I'm not sure why any script would prefer to rely on `onreadystatechange` in Opera (as non-sane as it is) in favor of the more reliable and semantically sensible `onload`.

As such, I would advise that Yandex should consider changing their feature test for relying on `readyState` to be:

var script = document.createElement("script");
var readyStateSupported = script.readyState && (script.readyState == "unloaded"
|| script.readyState == "uninitialized");

That *would* cause them to stop using `readyState` in current Opera, but if Opera eventually "fixes" their behavior to start out at "unloaded" (or "uninitialized" if they decide to support preloading), then it'd be available to Yandex again.
Comment 17 Ian 'Hixie' Hickson 2011-09-06 21:54:17 UTC
The order in IE seems to be: uninitialized loading loaded* interactive complete*.
It only fires the readystatechange event for those states marked with a *.
The "load" event fires after the last readystatechange and is synchronous with script execution.
The "interactive" state is set while the script is running.
Comment 18 Kyle Simpson 2011-09-06 22:07:10 UTC
(In reply to comment #17)
> The order in IE seems to be: uninitialized loading loaded* interactive
> complete*.
> It only fires the readystatechange event for those states marked with a *.
> The "load" event fires after the last readystatechange and is synchronous with
> script execution.
> The "interactive" state is set while the script is running.

According to my test, IE doesn't fire `onreadystatechange` for both "loaded" and "complete", only one or the other. I haven't inspected whether it changes the property without firing the event. That is quite funky if any UA's do that.
Comment 19 Ian 'Hixie' Hickson 2011-09-07 19:44:24 UTC
IE changes the property without firing an event for a whole bunch of cases.
Comment 20 Ian 'Hixie' Hickson 2011-09-07 19:45:56 UTC
Also it doesn't always seem to go to 'complete', and the behaviour seems to vary based on whether the script is empty, is a valid src="", an invalid src="", or is inline. I propose to simplify this in the spec so that there's comparatively little of IE's weirdness.
Comment 21 Boris Zbarsky 2011-09-07 19:56:06 UTC
> I saw the onload firing before the onreadystatechange

It looks like this was a measurement artifact.  If I log things more carefully, onreadystatechange to loaded (in the uncached case) or complete (in the cached case) seems to fire before onload.
Comment 22 Ian 'Hixie' Hickson 2011-09-07 20:10:01 UTC
Having studied this further, I propose to only fire the readystatechange event in the following situations:

 - when the prefetch has succeeded for an external script, if the script is not already being executed
 - when the prefetch has been abandoned, if the script is not already being executed (doesn't apply to any existing browser)
 - when 'error' is about to be fired (state will be 'complete')
 - when 'load' is about to be fired (state will be 'complete')

IE fires it in a couple of other cases (e.g. it fires it for the transition to 'loading' if the load hasn't started by the time the author tries to execute the script) but that just seems to make it noisy; I don't see a use case for exposing those transitions. It never seems to fire it for 'interactive'. It seems to use 'loaded' for uncached network loads and 'complete' for inline scripts (and according to bz, cached loads; I haven't tested it).

IE's behaviour if the src="" is something bogus like "foo:" or if it is something with special behaviour like "javascript:" is quite weird (the element can regress up the chain of states when the element is inserted into the document, for instance). I intend to drop all of that weird behaviour.
Comment 23 Ian 'Hixie' Hickson 2011-09-07 21:51:39 UTC
EDITOR'S RESPONSE: This is an Editor's Response to your comment. If you are satisfied with this response, please change the state of this bug to CLOSED. If you have additional information and would like the editor to reconsider, please reopen this bug. If you would like to escalate the issue to the full HTML Working Group, please add the TrackerRequest keyword to this bug, and suggest title and text for the tracker issue; or you may create a tracker issue yourself, if you are able to do so. For more details, see this document:
   http://dev.w3.org/html5/decision-policy/decision-policy.html

Status: Partially Accepted
Change Description: see diff given below
Rationale: Compatibility.
Comment 24 contributor 2011-09-07 21:56:45 UTC
Checked in as WHATWG revision r6543.
Check-in comment: Add script.readyState and fire 'readystatechange' in some cases. Not 100% IE compatible because IE is pretty inconsistent here. Should be compatible with legacy content though, both in browsers that do script prefetch and those that don't.
http://html5.org/tools/web-apps-tracker?from=6542&to=6543
Comment 25 Boris Zbarsky 2011-09-08 02:57:56 UTC
I was just implementing this.... when cloning an already-executed script, what should the readyState of the clone be set to?
Comment 26 Boris Zbarsky 2011-09-08 02:58:28 UTC
For that matter, what about cloning a script that's current loading or executing?
Comment 27 contributor 2011-09-09 22:46:11 UTC
Checked in as WHATWG revision r6550.
Check-in comment: Revert r6543 and instead move onreadystatechange to be only on Document.
http://html5.org/tools/web-apps-tracker?from=6549&to=6550