-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Enable global event dispatcher listeners to be lazily created. #19227
Enable global event dispatcher listeners to be lazily created. #19227
Conversation
@@ -420,12 +420,15 @@ moduleFor( | |||
constructor() { | |||
super(...arguments); | |||
|
|||
let dispatcher = this.owner.lookup('event_dispatcher:main'); | |||
run(dispatcher, 'destroy'); | |||
this.owner.__container__.reset('event_dispatcher:main'); |
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.
I was not able to keep this test unchanged, as the reference to the EventDispatcher
of the Glimmer Environment (already introduced in the initial PR) would always refer to the first instance, not the second one created here. So the curly component manager would still see the old dispatcher, that has no custom events added.
But I think this affects only our internal testing here, and not imply any publicly visible changes.
6d66813
to
7a88a69
Compare
I think something like this would work (for Component = CoreView.extend(...allTheMixinsInTheWorld, {
on(event) {
let eventDispatcher = getOwner(this).lookup<EventDispatcher>('event_dispatcher:main');
if (event in eventDispatcher.lazyEvents) {
dispatcher.setupHandlerForBrowserEvent(event);
}
return this._super(...arguments);
}
}); |
let eventDispatcher = owner.lookup<EventDispatcher>('event_dispatcher:main'); | ||
if ( | ||
eventDispatcher && | ||
Array.from(eventDispatcher.lazyEvents.values()).includes(eventName) |
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.
I don't think we can use Array.from or includes due to IE11 issues
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.
Yeah, already noticed that it breaks the Browserstack CI job. Will fix it later today...
Also .values()
btw
8875005
to
7073ef1
Compare
@rwjblue Thanks for the feedback. I added something like that, though we have to look for Ember's event names instead of browser event names. That's something which was slightly confusing throughout the whole PR. So as I was not entirely sure what kind of event name Also this latest change let me revert the previous changes to IE11 should also be happy now, as CI is green! |
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.
Awesome, thank you @simonihmig!
Looks like we need a rebase. Probably also need to circle around with @pzuraq about how we can reasonably thread the dispatcher through here now that |
Prior to this change, all events that would ever possibly be used would be eagerly setup by the event dispatcher's `setup` method. Unfortunately, this has (at least) two major downsides: * As of Chrome 51, adding listeners for touch events (`touchstart`, `touchmove`, `touchend`, etc) without the `passive` flag issue a performance focused warning. See https://www.chromestatus.com/feature/5745543795965952 for more details. * A number of the events that we have historically listened for fire **massive** numbers of events (`mouseenter`, `mousemove`, etc) and most applications do not really use these events. The two primary entry points into using the event dispatcher are the `action` element modifier and `Ember.Component`'s implementing the "event delegation" methods. This commit enables the event listeners in the event dispatcher to be lazily setup on demand by updating the action modifier manager and the curly component manager to invoke the event dispatchers `setupHandler` method lazily when needed.
7073ef1
to
9846858
Compare
9846858
to
229e3cb
Compare
Done.
I guess the reason we have passed the dispatcher through this extra thing was to give the component manager access to the dispatcher, without having access to So in the last commit added after the rebase I basically moved the previous lazy setup logic from the curly component manager's @rwjblue please re-review! |
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.
In general this seems good (and definitely does what we need).
I'm a tad worried about slowing down all @embed/component
instantiation though. Over the course of running the application I think that we would slowly reduce the number of events in lazyEvents
but I don't suspect that we will ever get that to be completely empty (since a large number of things just are not likely to be used IMHO).
Just to be clear: this is just as much (or less) a problem with the current solution (iterating over the lazy events in
Yeah, I understand and share that concern. Not sure how much of a real life problem this would be, but it's reasonable to try to mitigate this problem as much as possible... What about this idea: can we remember the component classes we already checked (for event methods), e.g. by having a That brings up another concern we already touched in your previous PR: our assumption that event handler methods may only be defined on the component's prototype. <MyComponent @keyPress={{this.handleKey}}/> Albeit a terrible pattern IMHO, I think this should work now with us checking for event methods in What we cannot support however is the even more weird case when you set an event handler method on the instance after instantiation. Can we safely assume this was never supposed to be supported? Or do we have to regard this as a breaking change, however unlikely such a pattern is? If so, we could expose the new laziness here in an optional feature maybe? Though that would lead to less users getting the benefits from this (when they forget to opt into or are not aware), and as such would not be that effective in fixing #15140. And I personally would like to not overcomplicate this PR if possible 😆 |
Yep, the two strategies are going to have similar / same cost I think. |
No, I don't think we have to support this. |
This skips over components whose class (prototype) has already been checked.
I tried this out, and it seems to work well, though the performance impact is not significant (see below). See the last commit added!
I tested this with a simple test app, that
The performance results (duration for those 1M rendered components) are as follows, all averaged over three separate runs:
So the results are extremely close, and probably all within the margin of error. The last optimization seems to have a slight positive effect, though still not very significant (given that these ~2s are for 1M component instantiations!). So not sure if these are worth the added code complexity? |
So as I could have anticipated, IE11 does not like my use of |
…nt-dispatching # Conflicts: # packages/@ember/-internals/glimmer/lib/component.ts # packages/@ember/-internals/glimmer/lib/modifiers/action.ts # packages/@ember/-internals/glimmer/lib/resolver.ts
7abe717
to
f61af5e
Compare
@rwjblue I updated this PR to the latest changes on master. The recent GlimmerVM update PR caused some conflicts, as you correctly anticipated. I hope this fix is ok, specifically this change here /cc @pzuraq Also referring to the performance optimizations (see above), I kept my little performance tweak and worked around the limitation of IE11 not supporting weak sets by (mis-)using a weak map. So CI is green now! 😅 Given that there were some changes and rebases after your last review, you will probably have to re-review? But I would certainly appreciate if we could land this asap, not keen for doing another rebase! 😉 |
…nt-dispatching # Conflicts: # packages/@ember/-internals/glimmer/lib/component.ts # packages/@ember/-internals/glimmer/lib/modifiers/action.ts # packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js # packages/@ember/-internals/views/lib/system/event_dispatcher.js
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.
Thanks for all the hard work here @simonihmig!
// Keep track of which component classes have already been processed for lazy event setup. | ||
// Using a WeakSet would be more appropriate here, but this can only be used when IE11 support is dropped. | ||
// Thus the workaround using a WeakMap<object, true> | ||
let lazyEventsProcessed = new WeakMap<EventDispatcher, WeakMap<object, true>>(); |
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.
FWIW, we have a WeakSet polyfill thingy that we use (which lets us treat it like a WeakSet
, but on IE11 is implemented as a WeakMap
).
import { _WeakSet as WeakSet } from '@glimmer/util';
Fixes #15140, supersedes #17911, makes emberjs/rfcs#675 unnecessary (see #15140 (comment))
This is based on the previous work by @rwjblue in #17911. Lazily copying his description here:
Updates to his PR:
<LinkTo>
andTextSupport
based<Input>
and<TextArea>
, see below for caveats...EventDispatcher#setupHandlerForBrowserEvent
andEventDispatcher#setupHandlerForEmberEvent
methods for more convenient (internal only!) useEventDispatcher
for props/methods supposed to be used internally, not for private ones in the OOP-sense (everything related toEventDispatcher
is still private in a SemVer sense)Evented
-based event listeners (this.on('click', ...)
), closing the unresolved issue belowUnresolved issuesAs can be seen in the changes required here for<LinkTo>
or<Input>
, the new lazy behavior is a breaking change whenever someone usedEmber.Component
'sEvented
mixin for adding DOM event listener likethis.on('click', this.handleClick)
, see this example ofTextSupport
I can't remember to have seen this documented for attaching browser event listeners, so not sure if this is supposed to be a public API we must not break? If so, we have to find a way to somehow sync the use ofthis.on()
toEventDispatcher#setupHandler
.Therefore marking this PR asWIP
as long as this is not sorted out. Otherwise good for review!