-
Notifications
You must be signed in to change notification settings - Fork 29.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
events: Handle a range of this values for dispatchEvent #33661
Conversation
From a quick experiment, it doesn’t look like the web uses |
You're right. Could you please point me somewhere else in node which does a similar thing? Or how it might be implemented? |
Yeah. The fun bit about that is that the browsers all seem to implement |
@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 |
@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. |
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. |
8ae28ff
to
2935f72
Compare
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 |
Note also that the only reason it works with a null or undefined thisArg is because the global object implements |
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 |
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. |
@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. |
@addaleax then I'm confused why this would be an issue for Or is this only an issue for |
@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. |
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 object
const childBody = 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, new Event('click'))
// logs 'inner'
</script> So access such a cross-realm object via same-origin iframes. Do |
Right – and for completeness, the Node.js equivalent of that, assuming that we make const { MessageChannel, moveMessagePortToContext } = require('worker_threads');
const { createContext } = require('vm');
const otherContext = createContext();
const { port1, port2 } = new MessageChannel();
const port2moved = moveMessagePortToContext(port2, otherContext);
const { dispatchEvent } = port1;
dispatchEvent.call(port2moved, new Event('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. |
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? |
@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, probably
const kSymbolEventTargetBrand = 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 |
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. |
@domenic I don’t think anybody was suggesting that – |
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. |
The only private field in EventTarget is Otherwise indeed things like |
70daf3e
to
0184632
Compare
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Zirak Thanks for bearing with us :)
@@ -417,6 +423,10 @@ function validateEventListenerOptions(options) { | |||
}; | |||
} | |||
|
|||
function isEventTarget(obj) { | |||
return obj && obj.constructor && obj.constructor[kIsEventTarget]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: We should likely include this explanation in a comment here on isEventTarget()
in case this ends up coming up in a question later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
lib/internal/event_target.js
Outdated
} | ||
} = require('internal/errors'); | ||
|
||
const perf_hooks = require('perf_hooks'); | ||
const { customInspectSymbol } = require('internal/util'); | ||
const { customInspectSymbol, kIsEventTarget } = require('internal/util'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: why define kIsEventTarget
in a separate file? It could just be included in this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
This will need a rebase |
20b3123
to
69e2720
Compare
On the web, dispatchEvent is finicky about its `this` value. An exception is thrown for `this` values which are not an EventTarget.
69e2720
to
b6e89ab
Compare
Closing in favor of #34015 (which combines this and other |
On the web, dispatchEvent is finicky about its
this
value. An exception isthrown for
this
values 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