You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
There is no active script when a user triggers an inline event handler #3295
This is somewhat similar to what was fixed by #3163. The fix is less obvious though. I guess we'd create a synthetic script representing the page's inline event handlers??
Which may well not be defined (I got a bit lost while digging)? But then I don't quite follow the assert added in #3163 which claims that jobs are only enqueued while a script is active; we can certainly enqueue jobs inside an event handler function, right?
Of course there's the extra fun of what happens with div.onclick = import.bind(stuff) which really doesn't have a scripted function anywhere around.
I've sort of lost track of how the ES spec uses "script or module" bits and why it thinks they should always exist, but it seems like fundamentally the issue is that ES thinks the only way you can start a new execution is by "running a script or evaluating a module", while in reality you can also start by calling a (possibly built-in) function directly. This seems like a pretty bad semantic mismatch between the ES spec and the web. :(
Which may well not be defined (I got a bit lost while digging)?
So, I guess the case is that if the page author has ever triggered the getter (e.g. just done el.onclick;), then the function is created at a time where an active script is present. But, if they never do, then this presumably happens in response to the user clicking, when there is no active script present.
But then I don't quite follow the assert added in #3163 which claims that jobs are only enqueued while a script is active; we can certainly enqueue jobs inside an event handler function, right?
Yeah, it seems like that's broken here too, as a result of this particular case.
Of course there's the extra fun of what happens with div.onclick = import.bind(stuff) which really doesn't have a scripted function anywhere around.
import() is more like super() in being a syntactic form, not a function, so this won't work. But your point stands, with e.g. eval.bind(undefined, "import(...)").
I've sort of lost track of how the ES spec uses "script or module" bits and why it thinks they should always exist
So, it's basically only to support import(). When you do import(), how do we resolve module specifiers? That's the only real consumer. Right now, the import() spec assumes (step 2) that GetActiveScriptOrModule is not null.
I think we have two paths:
Try to plug these holes, so that there's always an active script or module.
Create a fallback (besides asserting). Example:
import() throws a TypeError
import() resolves relative to the current settings object's API base URL
I am leaning toward the API base URL idea.
This would be observably equivalent to trying to plug the hole by creating some synthetic script for event handler purposes, but would also catch any other potential places we missed, and would be import()-specific.
ES thinks the only way you can start a new execution is by "running a script or evaluating a module", while in reality you can also start by calling a (possibly built-in) function directly. This seems like a pretty bad semantic mismatch between the ES spec and the web. :(
This is all relatively recent, so I wouldn't say this is a big fundamental problem; we can fix it.
I'd be curious from an implementer point of view which solution makes the most sense, between trying to maintain the invariant via synthetic scripts, versus using a fallback. For example, I could imagine that implementers might like the synthetic script approach, because it would help things like stack traces, the error event, and debuggers, which in their implementation probably also have a concept of "script". (And in the case of the error event, this correspondence kinda-sorta exists in the spec.)
Ah, if this is only relevant for import, that makes things a bit simpler...
So normally the url of the script is not used as a base url for things in the web platform. I agree that this is a little silly in various cases but especially for imports.
Falling back to the current settings object API base URL makes a lot of sense to me. That will ensure that things are always defined, and in the inline event handler case definitely produce the "right" behavior (use the URL of the page as the base URL).
This is all relatively recent
Gotcha. I agree that makes life simpler.
In terms of implementation, I'm not sure what works best. In SpiderMonkey, a non-builtin function always has an associated "script" which has a "filename" (typically a URL, but it's just an opaque string as far as SpiderMonkey is concerned, for the most part). For inline event handlers that we compile from a an attr value string, we set that "filename" to the document URL of the element's owner document at the moment when compilation happens.
import() resolves relative to the current settings object's API base URL
I'm personally a fan of this. In Chrome right now (< this link is outdated) we fallback to the document base URL, which I believe in this case, is equivalent to using the current settings object's API base URL.
Are we thinking the fix here is to make the import spec not Assert so harshly, and then in 8.1.3.8.2 just check to see if referencingScriptOrModule is null, and if so:
Yeah that's basically it. I think I would change "resolve a module specifier" to take a base URL instead of a script. Then we'd add a steps after step 1 that create baseURL and fetchOptions variables.
Also, I found a bug: step 4 just says "settings object" when it should probably say "referencing script's settings object". Or, in this fixed universe, we'd also create a settingsObject variable.
In Chrome right now we fallback to the document base URL, which I believe in this case, is equivalent to using the current settings object's API base URL.
I haven't looked into how exactly what your fallback code looks like, but use of "the document" is a red flag, because there are multiple documents around. Most simply, if you get the event handler string compiled while the node is in one document, then you adopt it into another one, now the current settings is whatever the document was where you compiled but "the document" is probably where you adopted into. Which one does Chrome actually use?
I looked into what Gecko does here. In Gecko, if you <div onclick="import(stuff)"> you get an exception:
SyntaxError: import declarations may only appear at top level of a module
so presumably we only allow import() at toplevel of a <script type="module"> or a module imported from such. Not sure whether that's a restriction that used to be in the spec but was then loosened?
The issue is that Gecko doesn't implement the import("x") expression syntax at all, so it thinks you're trying to do an import "x" statement, and gets confused.
Ok @bzbarsky to answer your question, in Chrome right now we use the base URL of the document the handler was first compiled/ran in. For example, if:
An event handler that contains import(...) is compiled/run in an iframe
The node with said event handler is adopted by an outer document
The node's event handler is evaluated again (onclick for example, we click again) in the outer document
...the "document's base URL" we use in subsequent evaluations of import(...) is that of the document in which the event handler was first compiled/run. Is this up to spec? From what you said it sounds like it is, but personally from what I'm reading it seems to make sense that adoption would change what the "current settings object" is (though atm I'm fairly unfamiliar with Realm records etc).
Also, I found a bug: step 4 just says "settings object" when it should probably say "referencing script's settings object"
Hmm, doesn't it say "referencing script's settings object" right now? Also, as you mentioned in the case where there is no referencing script and we'd have to create a settingsObject variable, I'm thinking this could just be the "current settings object", since it is that object that we're pulling the fallback base URL from right? And for "descendent script options" used, we'll probably have to initialize a new script fetch options struct with reasonable defaults since we have nothing to inherit from, or maybe we could define something similar to default classic script fetch options, differing only in the default credentials mode?
...the "document's base URL" we use in subsequent evaluations of import(...) is that of the document in which the event handler was first compiled/run. Is this up to spec?
This doesn't seem quite right. Right now the spec says to "crash" (it tries to get a property of the active script, and the active script is null). After the changes proposed here it will be the base URL of the node document of the attribute's owner element, which can be different from the one that's causing the click.
Also, as you mentioned in the case where there is no referencing script and we'd have to create a settingsObject variable, I'm thinking this could just be the "current settings object", since it is that object that we're pulling the fallback base URL from right?
Yeah, that was what I was thinking.
And for "descendent script options" used, we'll probably have to initialize a new script fetch options struct with reasonable defaults since we have nothing to inherit from, or maybe we could define something similar to default classic script fetch options, differing only in the default credentials mode?
I think we can just rename "default classic script fetch options" to "default script fetch options". The credentials mode should stay the same for both; changing that default is tracked by #3656.
@domfarolino interestingly it looks like we'll also need to fix HostResolveImportedModule, not just HostImportModuleDynamically. https://tc39.github.io/proposal-dynamic-import/ calls both of them with the active, and potentially null, script.
I skipped over an important comment of @bzbarsky's, while working on #4181:
But then I don't quite follow the assert added in #3163 which claims that jobs are only enqueued while a script is active; we can certainly enqueue jobs inside an event handler function, right?
This is still correct. In particular the trouble case is something like
plus the user actually clicking on the button. In this case there will be no active script when FunctionCreate is called in step 10 of get the current value of the event handler, which means the same is true when .then() calls EnqueueJob, which breaks the assert.
I will update #4181 to remove the assert and guard the related steps. This is in line with our conclusion that it's OK for there to not be an active script in these pathological cases, and instead have import() fall back to using the document's base URL.
Activity
annevk commentedon Dec 16, 2017
I suspect this is also of interest to @bzbarsky.
bzbarsky commentedon Dec 16, 2017
Hmm.... https://html.spec.whatwg.org/multipage/webappapis.html#the-event-handler-processing-algorithm calls https://heycam.github.io/webidl/#invoke-a-callback-function which calls https://tc39.github.io/ecma262/#sec-call
In this case, that lands in https://tc39.github.io/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist and https://tc39.github.io/ecma262/#sec-prepareforordinarycall and that will set things up with the [[ScriptOrModule]] of the function being called. That comes from https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler which does https://tc39.github.io/ecma262/#sec-functioncreate which calls https://tc39.github.io/ecma262/#sec-functioninitialize which relies on GetActiveScriptOrModule at that point.
Which may well not be defined (I got a bit lost while digging)? But then I don't quite follow the assert added in #3163 which claims that jobs are only enqueued while a script is active; we can certainly enqueue jobs inside an event handler function, right?
Of course there's the extra fun of what happens with
div.onclick = import.bind(stuff)which really doesn't have a scripted function anywhere around.I've sort of lost track of how the ES spec uses "script or module" bits and why it thinks they should always exist, but it seems like fundamentally the issue is that ES thinks the only way you can start a new execution is by "running a script or evaluating a module", while in reality you can also start by calling a (possibly built-in) function directly. This seems like a pretty bad semantic mismatch between the ES spec and the web. :(
domenic commentedon Dec 19, 2017
So, I guess the case is that if the page author has ever triggered the getter (e.g. just done
el.onclick;), then the function is created at a time where an active script is present. But, if they never do, then this presumably happens in response to the user clicking, when there is no active script present.Yeah, it seems like that's broken here too, as a result of this particular case.
import()is more likesuper()in being a syntactic form, not a function, so this won't work. But your point stands, with e.g.eval.bind(undefined, "import(...)").So, it's basically only to support
import(). When you doimport(), how do we resolve module specifiers? That's the only real consumer. Right now, the import() spec assumes (step 2) that GetActiveScriptOrModule is not null.I think we have two paths:
I am leaning toward the API base URL idea.
This would be observably equivalent to trying to plug the hole by creating some synthetic script for event handler purposes, but would also catch any other potential places we missed, and would be import()-specific.
This is all relatively recent, so I wouldn't say this is a big fundamental problem; we can fix it.
I'd be curious from an implementer point of view which solution makes the most sense, between trying to maintain the invariant via synthetic scripts, versus using a fallback. For example, I could imagine that implementers might like the synthetic script approach, because it would help things like stack traces, the
errorevent, and debuggers, which in their implementation probably also have a concept of "script". (And in the case of the error event, this correspondence kinda-sorta exists in the spec.)bzbarsky commentedon Dec 19, 2017
Ah, if this is only relevant for
import, that makes things a bit simpler...So normally the url of the script is not used as a base url for things in the web platform. I agree that this is a little silly in various cases but especially for imports.
Falling back to the current settings object API base URL makes a lot of sense to me. That will ensure that things are always defined, and in the inline event handler case definitely produce the "right" behavior (use the URL of the page as the base URL).
Gotcha. I agree that makes life simpler.
In terms of implementation, I'm not sure what works best. In SpiderMonkey, a non-builtin function always has an associated "script" which has a "filename" (typically a URL, but it's just an opaque string as far as SpiderMonkey is concerned, for the most part). For inline event handlers that we compile from a an attr value string, we set that "filename" to the document URL of the element's owner document at the moment when compilation happens.
domfarolino commentedon May 14, 2018
I'm personally a fan of this. In Chrome right now (< this link is outdated) we fallback to the document base URL, which I believe in this case, is equivalent to using the current settings object's API base URL.
The more I look around..could someone explain the difference between current settings object and entry settings object?
Are we thinking the fix here is to make the import spec not Assert so harshly, and then in 8.1.3.8.2 just check to see if
referencingScriptOrModuleis null, and if so:...or do I have this wrong?
annevk commentedon May 14, 2018
@domfarolino see the example in https://html.spec.whatwg.org/multipage/webappapis.html#realms-settings-objects-global-objects for an illustration of the difference between entry/incumbent/current/relevant (settings objects are 1:1 with Realms).
domenic commentedon May 14, 2018
Yeah that's basically it. I think I would change "resolve a module specifier" to take a base URL instead of a script. Then we'd add a steps after step 1 that create baseURL and fetchOptions variables.
Also, I found a bug: step 4 just says "settings object" when it should probably say "referencing script's settings object". Or, in this fixed universe, we'd also create a settingsObject variable.
Are you, uh, volunteering? :)
bzbarsky commentedon May 14, 2018
I haven't looked into how exactly what your fallback code looks like, but use of "the document" is a red flag, because there are multiple documents around. Most simply, if you get the event handler string compiled while the node is in one document, then you adopt it into another one, now the current settings is whatever the document was where you compiled but "the document" is probably where you adopted into. Which one does Chrome actually use?
I looked into what Gecko does here. In Gecko, if you
<div onclick="import(stuff)">you get an exception:so presumably we only allow
import()at toplevel of a<script type="module">or a module imported from such. Not sure whether that's a restriction that used to be in the spec but was then loosened?domenic commentedon May 14, 2018
The issue is that Gecko doesn't implement the
import("x")expression syntax at all, so it thinks you're trying to do animport "x"statement, and gets confused.domfarolino commentedon May 16, 2018
Indeed! I'd like to take a look at this this weekend, I should have more time then.
Ahh, I'm not sure actually. I'll dig into this a bit too but can't guarantee a timely answer to that one.
domfarolino commentedon May 27, 2018
Ok @bzbarsky to answer your question, in Chrome right now we use the base URL of the document the handler was first compiled/ran in. For example, if:
import(...)is compiled/run in an iframeonclickfor example, we click again) in the outer document...the "document's base URL" we use in subsequent evaluations of
import(...)is that of the document in which the event handler was first compiled/run. Is this up to spec? From what you said it sounds like it is, but personally from what I'm reading it seems to make sense that adoption would change what the "current settings object" is (though atm I'm fairly unfamiliar with Realm records etc).domfarolino commentedon May 27, 2018
Hmm, doesn't it say "referencing script's settings object" right now? Also, as you mentioned in the case where there is no referencing script and we'd have to create a
settingsObjectvariable, I'm thinking this could just be the "current settings object", since it is that object that we're pulling the fallback base URL from right? And for "descendent script options" used, we'll probably have to initialize a new script fetch options struct with reasonable defaults since we have nothing to inherit from, or maybe we could define something similar to default classic script fetch options, differing only in the default credentials mode?domenic commentedon May 30, 2018
This doesn't seem quite right. Right now the spec says to "crash" (it tries to get a property of the active script, and the active script is null). After the changes proposed here it will be the base URL of the node document of the attribute's owner element, which can be different from the one that's causing the click.
This follows from https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler step 9/10, which establish that the function is created in the node document of the attribute's owner element.
Yeah, this got fixed by #3700.
Yeah, that was what I was thinking.
I think we can just rename "default classic script fetch options" to "default script fetch options". The credentials mode should stay the same for both; changing that default is tracked by #3656.
domenic commentedon Jul 9, 2018
@domfarolino interestingly it looks like we'll also need to fix HostResolveImportedModule, not just HostImportModuleDynamically. https://tc39.github.io/proposal-dynamic-import/ calls both of them with the active, and potentially null, script.
Editorial: change "resolve a module specifier" to take a URL
domfarolino commentedon Jul 9, 2018
Good catch, will update that too.
Handle module resolution when there is no active script
domenic commentedon Nov 20, 2018
I skipped over an important comment of @bzbarsky's, while working on #4181:
This is still correct. In particular the trouble case is something like
plus the user actually clicking on the button. In this case there will be no active script when FunctionCreate is called in step 10 of get the current value of the event handler, which means the same is true when .then() calls EnqueueJob, which breaks the assert.
I will update #4181 to remove the assert and guard the related steps. This is in line with our conclusion that it's OK for there to not be an active script in these pathological cases, and instead have import() fall back to using the document's base URL.
Handle module resolution when there is no active script
Handle module resolution when there is no active script
Handle module resolution when there is no active script
Editorial: change "resolve a module specifier" to take a URL
Handle module resolution when there is no active script
Handle module resolution when there is no active script