Uh oh!
There was an error while loading. Please reload this page.
events: Handle a range of this values for dispatchEvent - #33661
Conversation
addaleax
commented
May 30, 2020
From a quick experiment, it doesn’t look like the web uses |
Zirak
commented
May 30, 2020
You're right. Could you please point me somewhere else in node which does a similar thing? Or how it might be implemented? |
jasnell
commented
May 30, 2020
Yeah. The fun bit about that is that the browsers all seem to implement |
addaleax
commented
May 30, 2020
@Zirak I don’t know, I assumed browsers follow a certain spec on that point but I’m not sure where that is or where to find it. @benjamingr might know.
Yeah, the reason I commented was that we may want to look into moving our |
benjamingr
commented
May 30, 2020
@Zirak I recommend you use a symbol and check for its presence, that could work. Thanks by the way I am in favor for this change with the reservations Anna raised. |
Zirak
commented
May 30, 2020
A simple symbol ( Using To take a step back from the global-ness of To summarise, we can:
In my opinion, anything below the first option seems overkill for this point in time. |
benjamingr
commented
May 31, 2020
8ae28ff to
2935f72Comparedomenic
commented
May 31, 2020
This behavior is part of Web IDL; see https://heycam.github.io/webidl/#dfn-create-operation-function step 2.1.2. (And thus it's part of the definition of all methods on the web platform, including those of I'd suggest Node does whatever it currently does for |
domenic
commented
May 31, 2020
Note also that the only reason it works with a null or undefined thisArg is because the global object implements |
benjamingr
commented
May 31, 2020
Node URL doesn't have any methods except toJSON which uses a symbol and doesn't work cross realm. Node is (probably unknowingly) violating the URL spec here (in a small way). I think this would have to be solved in C++ since cross realm branding isn't possible without a well known global. Probably a |
domenic
commented
May 31, 2020
Getters and setters also have a brand check Workers can't synchronously call methods on other-thread objects so cross-thread is not a concern, at least on the web. |
benjamingr
commented
May 31, 2020
@domenic I assumed URLs were transferable like I'd still like Node to fix this for |
Transferable objects still don't have this problem. You still can't call methods/getters/setters across realms. The transferred object now lives in the destination realm. |
benjamingr
commented
Jun 1, 2020
@addaleax then I'm confused why this would be an issue for Or is this only an issue for |
addaleax
commented
Jun 1, 2020
@benjamingr In browsers, you can call any I know it’s kind of an edge-case, but the same should work in Node.js, I would say. |
domenic
commented
Jun 1, 2020
I think there is still some confusion. Can you give a code example? Note that if you transfer a |
If I understood @addaleax correctly, the concern on the web is something like: <iframe></iframe><script>// childBody is a cross-realm objectconstchildBody=document.querySelector('iframe').contentDocument.body;document.body.onclick=(e)=>console.log('outer',e);childBody.onclick=(e)=>console.log('inner',e);document.body.dispatchEvent.call(childBody,newEvent('click'))// logs 'inner'</script>So access such a cross-realm object via same-origin iframes. Do |
addaleax
commented
Jun 1, 2020
Right – and for completeness, the Node.js equivalent of that, assuming that we make const{ MessageChannel, moveMessagePortToContext }=require('worker_threads');const{ createContext }=require('vm');constotherContext=createContext();const{ port1, port2 }=newMessageChannel();constport2moved=moveMessagePortToContext(port2,otherContext);const{ dispatchEvent }=port1;dispatchEvent.call(port2moved,newEvent('message')); |
I see, yes, if vm contexts are involved, then the concern arises. I thought the discussion was purely about actual threads (created by workers) and thread-safety, not vm contexts. |
Zirak
commented
Jun 1, 2020
Very interesting, I didn't know about that, thanks! It seems like a cpp helper using something like a How about a pair of helper native functions, one to brand the passed object as an event emitter, and another to check the branding? Is there a file or directory they'd be home in? |
addaleax
commented
Jun 1, 2020
@Zirak That’s possible, yes. I’d probably just stick those helpers in I don’t want to bikeshed this too much, but there is one problem with that approach, and that is performance. Boundary crossings between C++ and JS are expensive, and a brand check that calls into C++ would have a non-trivial performance impact. That’s why I upvoted #33661 (comment), and maybe I should have been more explicit about it – I liked not only the summary but also the conclusion, namely that |
I think a symbol would be a reasonable alternative, under the Node.js "namespace": const{ SymbolFor }=primordials;// put on the prototype, probablyconstkSymbolEventTargetBrand=Symbol.for('nodejs.event_target.brand');Or something similar. I share Anna's concern regarding the performance penalty of a C++ call, I think "leaking" a symbol prefixed with |
domenic
commented
Jun 1, 2020
Note that if you're planning to use private fields, then you will not be able to work cross-realm anyway. Private fields have a built-in same-realm brand check. It might be simpler for Node to disallow cross-realm method invocation, at least as long as things are implemented in JS. |
addaleax
commented
Jun 1, 2020
@domenic I don’t think anybody was suggesting that – |
domenic
commented
Jun 1, 2020
But EventTarget currently uses private fields, so you would have to remove all of that usage if you wanted to make it work cross-realm. |
benjamingr
commented
Jun 1, 2020
The only private field in EventTarget is Otherwise indeed things like |
70daf3e to
0184632Compare
Great, I'll take any reason to not write cpp :) Addressed with a call to Thanks all for your guidance and patience. |
There was a problem hiding this comment.
It's not clear how fool-proof you want to be here, but I'll note that people can fool this brand check. For example, EventTarget.prototype.dispatchEvent.call({ constructor: EventTarget }) would pass with this implementation, but fail per spec.
But anything you do will probably be a compromise. If something works cross-realm, then it can be fooled (and thus is not spec-compliant). If it doesn't work cross-realm, then it is not spec compliant either.
So without C++ there will always be compromises. Maybe this version is a fine compromise.
There was a problem hiding this comment.
Nit: We should likely include this explanation in a comment here on isEventTarget() in case this ends up coming up in a question later.
benjamingr
left a comment
There was a problem hiding this comment.
LGTM, would be interested in seeing a benchmark run with this - but I think this is the cheapest option we have for the brand check.
We should bring this issue up to V8 and in the summit as it's likely to return again.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Nit: why define kIsEventTarget in a separate file? It could just be included in this file?
There was a problem hiding this comment.
From looking around the node codebase, it seemed like some calls to SymbolFor happened there. On another glance, it looks like there's more usage inside files, so np, I'll move it in here.
jasnell
commented
Jun 17, 2020
This will need a rebase |
20b3123 to
69e2720CompareOn the web, dispatchEvent is finicky about its `this` value. An exception is thrown for `this` values which are not an EventTarget.
jasnell
commented
Jun 22, 2020
Closing in favor of #34015 (which combines this and other |
On the web, dispatchEvent is finicky about its
thisvalue. An exception isthrown for
thisvalues which are not an EventTarget. However, null-ishvalues (null and undefined) do not throw and return immediately.
Checklist
make -j4 test(UNIX), orvcbuild test(Windows) passes