Skip to content
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: make abort_controller event trusted #35811

Closed

Conversation

benjamingr
Copy link
Member

Fire abort event as trusted, fixes #35748

Checklist
  • [ x make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

get: isTrusted,
get() {
return this[kTrustEvent];
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid this:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@addaleax given these conflicting requirements (unforgeable and the fact we need some events to be trusted internally) - what should we do?

Regarding performance - I am not sure the performance of isTrusted for EventTarget is that important (is it?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@addaleax hmm, actually I can capture it from the closure - let me try and let me know what you think - though it will likely tank performance just as bad.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure the performance of isTrusted for EventTarget is that important (is it?)

It’s not the performance of isTrusted, it’s the performance of new Event() that matters here :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@addaleax please take a look. I think what I ended up doing shouldn't tank performance or introduce a closure. Should I run benchmarks?

(Also thanks, I didn't know this tanks perf 🙏 )

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I run benchmarks?

I think that would be helpful, yes :) And maybe also update the benchmarks to account for situations in which sometimes new Event() creates a trusted event and sometimes not :)

Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM if the benchmarks are good with it :)

@benjamingr
Copy link
Member Author

benjamingr commented Oct 26, 2020

Looks mostly OK:

benjamingruenbaum@Benjamins-MBP node % git checkout fix-abortcontroller-trusted-event
Switched to branch 'fix-abortcontroller-trusted-event'
benjamingruenbaum@Benjamins-MBP node % make -j12 > /dev/null                         
benjamingruenbaum@Benjamins-MBP node % ./out/Release/node ./benchmark/events/eventtarget.js
events/eventtarget.js listeners=1 n=1000000: 3,067,260.432666775
events/eventtarget.js listeners=5 n=1000000: 2,797,382.2678592885
events/eventtarget.js listeners=10 n=1000000: 2,318,102.157338759
benjamingruenbaum@Benjamins-MBP node % git checkout master
Switched to branch 'master'
benjamingruenbaum@Benjamins-MBP node % make -j12 > /dev/null 
benjamingruenbaum@Benjamins-MBP node % ./out/Release/node ./benchmark/events/eventtarget.js
events/eventtarget.js listeners=1 n=1000000: 3,144,785.5756887244
events/eventtarget.js listeners=5 n=1000000: 2,704,624.177365229
events/eventtarget.js listeners=10 n=1000000: 2,340,452.7737892433
benjamingruenbaum@Benjamins-MBP node % 

Just for sport - here are benchmarks with the "old" (property) fix:

benjamingruenbaum@Benjamins-MBP node % ./out/Release/node ./benchmark/events/eventtarget.js
events/eventtarget.js listeners=1 n=1000000: 1,039,763.9506252201
events/eventtarget.js listeners=5 n=1000000: 937,062.0643370639
events/eventtarget.js listeners=10 n=1000000: 829,347.3311018195

I ran each a few times (but didn't run the t-test thing). Anyway - good catch @addaleax :]

Comment on lines 63 to 64
const isTrustedFalse = () => false;
const isTrustedTrue = () => true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use a SafeWeakSet, so that the isTrusted getter function between a trusted and untrusted event is the same:

Object.getOwnPropertyDescriptor(trustedEvent, "isTrusted").get === Object.getOwnPropertyDescriptor(untrustedEvent, 'isTrusted').get;
Suggested change
const isTrustedFalse = () => false;
const isTrustedTrue = () => true;
const isTrustedSet = new SafeWeakSet();
const isTrusted = ObjectGetOwnPropertyDescriptor({
get isTrusted() {
return isTrustedSet.has(this);
}
}, 'isTrusted').get;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see Anna's comment above - this would introduce a (rather big) performance regression. The initial implementation was a bit like this (except without a WeakSet which IIRC incurs its own separate performance penalty in 8).

That said - that's a very good point (regarding wanting the reference to be the same for false/true cases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, @addaleax I would like to (if possible) make the issue @ExE-Boss raised work, namely:

Object.getOwnPropertyDescriptor(trustedEvent, "isTrusted").get === Object.getOwnPropertyDescriptor(untrustedEvent, 'isTrusted').get;

Any clever trick or idea for getting the reference of different functions to equal without creating an additional closure for every event target or running into the "have a non-configurable own accessor property on an object without the overhead of creating the dictionary for slow properties" thing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm … I think the above suggestion by @ExE-Boss might work rather well, actually? If we always use the same getter, then performance-wise we’re good, and SafeWeakSet should give us the guarantees we’d want here in terms of unforgeability

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I think I misunderstood the solution - this is actually pretty clever (creating the temp object and getting the reference to avoid the different property.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarks look good.

// isTrusted is special (LegacyUnforgeable)
ObjectDefineProperty(this, 'isTrusted', {
get: isTrusted,
get: shouldTrust ? isTrustedTrue : isTrustedFalse,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
get: shouldTrust ? isTrustedTrue : isTrustedFalse,
get: isTrusted,

@@ -75,9 +77,11 @@ class Event {
this[kDefaultPrevented] = false;
this[kTimestamp] = lazyNow();
this[kPropagationStopped] = false;
const shouldTrust = (options != null && options[kTrustEvent]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const shouldTrust = (options != null && options[kTrustEvent]);
if (options != null && options[kTrustEvent]) {
isTrustedSet.add(this);
}

@benjamingr
Copy link
Member Author

PTAL @ExE-Boss - neat trick 🙏

@benjamingr benjamingr force-pushed the fix-abortcontroller-trusted-event branch from d66fba0 to 2bc7831 Compare October 26, 2020 12:52
@benjamingr
Copy link
Member Author

@ExE-Boss

P.S. since I applied the changes manually and I feel like your contribution to this PR was important - I have added you as Co-Authored-By (same as if I pressed ""Commit Suggestion" in GitHub when squashing).

I have used the same username/email combo GitHub used for you when this happened in the past - but if you prefer a different email/name combo please let me know and I'll gladly change it to whatever. Alternatively if you want me to remove you as "Co-Authored-By" let me know and I will do that instead. Whatever you prefer.

@benjamingr benjamingr force-pushed the fix-abortcontroller-trusted-event branch from ebec379 to 7fc3d02 Compare October 26, 2020 14:01
@jasnell
Copy link
Member

jasnell commented Oct 26, 2020

The docs should be updated also: See: https://nodejs.org/dist/latest-v15.x/docs/api/events.html#events_event_istrusted

Copy link
Member

@Trott Trott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with any necessary doc updates applied

@benjamingr
Copy link
Member Author

benjamingr commented Oct 26, 2020

Any idea on what the docs should say?

> Type: {boolean} Returns `false` for user constructed events and `true` for Node.js internal events. 

Currently only `AbortSignal`s' `"abort"` event is fired with `isTrusted` set to `true`.

Or something?

@jasnell
Copy link
Member

jasnell commented Oct 26, 2020

That works, I think. We can revisit it later if necessary.

Comment on lines 11 to 14
Symbol,
SymbolFor,
SafeWeakSet,
ObjectGetOwnPropertyDescriptor,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be sorted alphabetically:

Suggested change
Symbol,
SymbolFor,
SafeWeakSet,
ObjectGetOwnPropertyDescriptor,
ObjectGetOwnPropertyDescriptor,
SafeWeakSet,
Symbol,
SymbolFor,

get isTrusted() {
return isTrustedSet.has(this);
}
}, 'isTrusted').get;

class Event {
constructor(type, options) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To ensure that the Event constructor has the correct length, options should be initialised to null:

Suggested change
constructor(type, options) {
constructor(type, options = null) {

This also makes it possible to use strict equality comparison when checking options !== null.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like a separate change from this one - @jasnell given past experience - would you prefer it if I made it here or if we opened an issue and it went on a separate PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either works I think. If you do it here make it a separate Commit but a separate pr also works

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I don't understand this - IIUC length is determined by the number of arguments - so Event.length should be 2 anyway.

I can add a test for that though - I'll push it in #35806

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@ExE-Boss ExE-Boss Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benjamingr

The length property defaults to the number of leading parameters without an initializer, and options = null makes the parameter have an initializer, thus the length will be 1:

https://tc39.es/ecma262/#sec-function-definitions-static-semantics-expectedargumentcount

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed it in the other PR :]

@benjamingr benjamingr force-pushed the fix-abortcontroller-trusted-event branch 2 times, most recently from ed336bd to 0e3da51 Compare October 26, 2020 16:13
Comment on lines +67 to +72
const isTrusted = ObjectGetOwnPropertyDescriptor({
get isTrusted() {
return isTrustedSet.has(this);
}
}, 'isTrusted').get;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const isTrusted = ObjectGetOwnPropertyDescriptor({
get isTrusted() {
return isTrustedSet.has(this);
}
}, 'isTrusted').get;
function isTrusted() {
return isTrustedSet.has(this);
}

Copy link
Contributor

@ExE-Boss ExE-Boss Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will make the isTrusted getter function incorrectly have an own prototype property and the [[Construct]] internal method.

It will also cause it to have the wrong name.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ExE-Boss would you mind explaining why this doesn't work?

Copy link
Contributor

@ExE-Boss ExE-Boss Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the following will no longer throw a TypeError:

new (Object.getOwnPropertyDescriptor(new Event('foo'), "isTrusted").get)();

And the following will return "object" instead of "undefined":

typeof Object.getOwnPropertyDescriptor(new Event('foo'), "isTrusted").get.prototype;

And the following will return true instead of false:

Object.getOwnPropertyDescriptor(new Event('foo'), "isTrusted").get.hasOwnProperty("prototype");

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a test for this across the EventTarget properties :]

@benjamingr benjamingr force-pushed the fix-abortcontroller-trusted-event branch from 565cb05 to 521bc99 Compare October 26, 2020 16:19
@@ -1282,9 +1282,11 @@ This is not used in Node.js and is provided purely for completeness.
added: v14.5.0
-->

* Type: {boolean} Always returns `false`.
* Type: {boolean} Returns `false` for user constructed events and `true`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Type: {boolean} Returns `false` for user constructed events and `true`
* Type: {boolean} Value is `false` for user-constructed events and `true`

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All other properties (like event.returnValue) just use "True if..." so I will change to that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that works.

@Trott
Copy link
Member

Trott commented Oct 26, 2020

Benchmark CI: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/673/

Update: Results show no significant change:

10:08:54                                               confidence improvement accuracy (*)   (**)   (***)
10:08:54  events/eventtarget.js listeners=10 n=1000000                 3.45 %       ±5.08% ±6.82%  ±9.02%
10:08:54  events/eventtarget.js listeners=1 n=1000000                 -2.23 %       ±4.07% ±5.47%  ±7.22%
10:08:54  events/eventtarget.js listeners=5 n=1000000                  6.69 %       ±7.09% ±9.49% ±12.47%
10:08:54 

@@ -4,8 +4,10 @@
const common = require('../common');

const { ok, strictEqual } = require('assert');
const { Event } = require('internal/event_target');
Copy link
Contributor

@ExE-Boss ExE-Boss Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is unnecessary, as the Event constructor is available as a global property.

Suggested change
const { Event } = require('internal/event_target');

It also causes the test to fail without the ‑‑expose‑internals flag.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event is currently not exposed globally as far as I know. Good catch on the flag - I'll add that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Event is not exposed at all at the moment because as you can tell - there are still a bunch of rough edges we need to iron out - I intended to work on this a lot more a few months ago and am only getting to it now)

Copy link
Contributor

@ExE-Boss ExE-Boss Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event is exposed since #35496, which shipped in v15.0.0:

const {
EventTarget,
Event,
} = require('internal/event_target');
exposeInterface(global, 'EventTarget', EventTarget);
exposeInterface(global, 'Event', Event);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/ping @benjamingr Good to make this change or not so much?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no strong feelings regarding this - I didn't realize that we expose event globally and totally missed it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ExE-Boss very cool I missed this!

The AbortController abort event is trusted, currently we fire all
events with isTrusted: false. Allow dispatching events
internally with `isTrusted: true` and add a test for it.

Co-Authored-By: ExE Boss <3889017+ExE-Boss@users.noreply.github.com>
Fixes: nodejs#35748
@benjamingr benjamingr force-pushed the fix-abortcontroller-trusted-event branch from 521bc99 to 5ccc2a9 Compare October 26, 2020 18:43
@benjamingr
Copy link
Member Author

Thanks for the review and benchmark run Trott - please take a look at the wording :]

@benjamingr benjamingr added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Oct 28, 2020
@Trott
Copy link
Member

Trott commented Oct 28, 2020

@Trott what is the process now? Do I just add the commit queue label?

You can apply the commit-queue label on a PR like this, but it will fail because CI has not been run. There's a label for that too, though! request-ci

I'll add the request-ci label now.

In general, there are a number of cases where commit-queue can fail and you will need to manually land or at least adjust things. I don't use the commit-queue label much (yet!) so I'm not familiar with all of them, but I'm sure others who use it have a pretty good handle on when it works and when it doesn't.

The up-to-date process for landing a PR would ideally be in https://github.com/nodejs/node/blob/master/doc/guides/collaborator-guide.md#landing-pull-requests but I see that the commit-queue label is not mentioned there, so uh...it's still good if you need to go the manual route.

@Trott Trott added the request-ci Add this label to start a Jenkins CI on a PR. label Oct 28, 2020
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Oct 28, 2020
@nodejs-github-bot
Copy link
Collaborator

@benjamingr
Copy link
Member Author

@Trott ok - I see there are two failed tests in the CI that are unrelated - how should I proceed?

@Trott
Copy link
Member

Trott commented Oct 28, 2020

@Trott ok - I see there are two failed tests in the CI that are unrelated - how should I proceed?

Best thing to do with unrelated failures is to use "Resume Build" on the left nav. That will rebuild only the Jenkins subtasks that failed before. I'll go ahead and click "Resume Build" right now so you don't have to.

Optional thing you can do if you want to be a good citizen: Check to make sure that there are open issues for the tests that failed.

  • If not, open an issue.
  • If there are open issues and they haven't been updated in a while, consider leaving a comment and a copy/paste of the error trace so that it's clear that the issue is ongoing (and a recent trace will be more accurate/useful than a dated one).
  • If there are open issues and they have been updated recently, consider opening a PR to mark the test flaky.

@nodejs-github-bot
Copy link
Collaborator

@benjamingr benjamingr added the commit-queue Add this label to land a pull request using GitHub Actions. label Oct 28, 2020
@github-actions github-actions bot removed the commit-queue Add this label to land a pull request using GitHub Actions. label Oct 28, 2020
@github-actions
Copy link
Contributor

Landed in 81ba3ae...f20a783

@github-actions github-actions bot closed this Oct 28, 2020
nodejs-github-bot pushed a commit that referenced this pull request Oct 28, 2020
The AbortController abort event is trusted, currently we fire all
events with isTrusted: false. Allow dispatching events
internally with `isTrusted: true` and add a test for it.

Co-Authored-By: ExE Boss <3889017+ExE-Boss@users.noreply.github.com>
Fixes: #35748

PR-URL: #35811
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
@benjamingr benjamingr deleted the fix-abortcontroller-trusted-event branch October 28, 2020 19:28
@benjamingr
Copy link
Member Author

@Trott it worked - this is pretty cool!

targos pushed a commit that referenced this pull request Nov 3, 2020
The AbortController abort event is trusted, currently we fire all
events with isTrusted: false. Allow dispatching events
internally with `isTrusted: true` and add a test for it.

Co-Authored-By: ExE Boss <3889017+ExE-Boss@users.noreply.github.com>
Fixes: #35748

PR-URL: #35811
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
@targos targos mentioned this pull request Nov 3, 2020
targos pushed a commit to targos/node that referenced this pull request Apr 24, 2021
The AbortController abort event is trusted, currently we fire all
events with isTrusted: false. Allow dispatching events
internally with `isTrusted: true` and add a test for it.

Co-Authored-By: ExE Boss <3889017+ExE-Boss@users.noreply.github.com>
Fixes: nodejs#35748

PR-URL: nodejs#35811
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
targos pushed a commit to targos/node that referenced this pull request Apr 26, 2021
The AbortController abort event is trusted, currently we fire all
events with isTrusted: false. Allow dispatching events
internally with `isTrusted: true` and add a test for it.

Co-Authored-By: ExE Boss <3889017+ExE-Boss@users.noreply.github.com>
Fixes: nodejs#35748

PR-URL: nodejs#35811
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
targos pushed a commit to targos/node that referenced this pull request Apr 30, 2021
The AbortController abort event is trusted, currently we fire all
events with isTrusted: false. Allow dispatching events
internally with `isTrusted: true` and add a test for it.

Co-Authored-By: ExE Boss <3889017+ExE-Boss@users.noreply.github.com>
Fixes: nodejs#35748

PR-URL: nodejs#35811
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
targos pushed a commit that referenced this pull request Apr 30, 2021
The AbortController abort event is trusted, currently we fire all
events with isTrusted: false. Allow dispatching events
internally with `isTrusted: true` and add a test for it.

Co-Authored-By: ExE Boss <3889017+ExE-Boss@users.noreply.github.com>
Fixes: #35748

PR-URL: #35811
Backport-PR-URL: #38386
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
@targos targos added backported-to-v14.x and removed author ready PRs that have at least one approval, no pending requests for changes, and a CI started. backport-open-v14.x labels Apr 30, 2021
@danielleadams danielleadams mentioned this pull request May 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
events Issues and PRs related to the events subsystem / EventEmitter.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

'abort' event on AbortSignal should be isTrusted: true
9 participants