-
Notifications
You must be signed in to change notification settings - Fork 424
fix(engine-dom): do not leak pivot constructor #3112
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
Conversation
| adoptedCallback?: () => void; | ||
| attributeChangedCallback?: (name: string, oldValue: any, newValue: any) => void; | ||
| observedAttributes: Set<string>; | ||
| } |
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 just decided to move this up to have all the TypeScript types/interfaces together.
| const globalDefinitionsByTag = new Map<string, Definition>(); | ||
| const globalDefinitionsByClass = new Map<CustomElementConstructor, Definition>(); | ||
| const awaitingUpgrade = new Map<string, Set<HTMLElement>>(); | ||
| const pendingWhenDefinedCallbacks = new Map<string, WhenDefinedCallback[]>(); |
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 considered keeping track of the Promise as well as the resolve function, so that we didn't need an array of callbacks and could just keep returning the same Promise, but it got really hairy to deal with the Promise constructor callbacks. An array was a lot simpler.
| expect(Ctor).not.toBeUndefined(); | ||
| expect(Ctor).toBe(firstCtor); | ||
| }); | ||
| }); |
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.
These tests may seem excessive, but it's to get 100% code coverage.
| } | ||
| } | ||
| // If anyone called customElements.whenDefined() and is still waiting for a promise resolution, resolve now | ||
| flushPendingWhenDefinedCallbacks(tagName, constructor); |
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.
Question:
If the user register's a whenDefined callback for 'x-foo' and it is the engine registers its own constructor for x-foo. The user's callback will still be invoked in this flush operation, correct? Is that the desired operation? This would lead to inconsistency in behavior of the APIs.
// user registers callback for x-foo
// engine defines x-foo with a pivot ctor unrelated to user
// user's callback is flushed
// Now user thin's the element is defined, runs customElements.get('x-foo') and I believe an `undefined` is returned
The meta question is, should the engine's usage of the native registry to perform pivoting be transparent to the user?
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.
nvm, this case is handled in L492
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 can add a test for the negative case, to confirm that the promise never resolves.
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.
Nevermind, there is a test for that here: https://github.com/salesforce/lwc/pull/3112/files#r997496983
| const promise = customElements.whenDefined(tagName).then((ctor) => { | ||
| resolvedCtor = ctor; | ||
| }); | ||
| expect(customElements.get(tagName)).toBeUndefined(); |
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.
Between L66 and L71, the customElements registry is untouched except for registering the callback. In which case is it expected that customElements.get(tagName) will behave differently at L71?
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.
No you're right, this line is unnecessary.
| return Promise.resolve() | ||
| .then(() => { | ||
| expect(resolvedCtor).toEqual(undefined); // whenDefined does not leak pivot constructor | ||
| customElements.define(tagName, MyCustomComponent); | ||
| return promise; |
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.
| return Promise.resolve() | |
| .then(() => { | |
| expect(resolvedCtor).toEqual(undefined); // whenDefined does not leak pivot constructor | |
| customElements.define(tagName, MyCustomComponent); | |
| return promise; | |
| expect(resolvedCtor).toEqual(undefined); // whenDefined does not leak pivot constructor | |
| customElements.define(tagName, MyCustomComponent); | |
| return promise. |
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.
See https://github.com/salesforce/lwc/pull/3112/files#r997496983.
I'll add a comment about why Promise.resolve() is in there.
| expect(Ctor).not.toBeUndefined(); | ||
| expect(Ctor).toBe(firstCtor); | ||
| }); | ||
| it('whenDefined() should always return the same constructor - defined before whenDefined', () => { |
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('whenDefined() should always return the same constructor - defined before whenDefined', () => { | |
| it('whenDefined() should always return the same constructor - pivot defined before whenDefined', () => { |
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.
This test actually runs with and without pivots.
| }); | ||
| }); | ||
|
|
||
| it('whenDefined() should always return the same constructor - defined after whenDefined', () => { |
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.
Same as above - additional comments
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.
See above; I'll add a comment
ravijayaramappa
left a comment
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, just some questions and minor comments
| class MyCustomComponent extends HTMLElement {} | ||
| return Promise.resolve() | ||
| .then(() => { | ||
| expect(resolvedCtor).toEqual(undefined); // whenDefined does not leak pivot constructor |
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.
This ^ verifies that whenDefined() does not leak a private element created by LWC. (At least insofar as we assume Promise.resolve() would have caught it)
…ex.spec.js Co-authored-by: Ravi Jayaramappa <ravi.jayaramappa@salesforce.com>
…s-pivots-undefined-2
|
/nucleus test |
3 similar comments
|
/nucleus test |
|
/nucleus test |
|
/nucleus test |
Details
Fixes #3073.
Hides the pivot constructor as best we can, when running in
ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRYmode. This better prevents users from "detecting" that the pivot patches are applied.We can't fully fix this (see details in #3073), so this is a "best effort."
Does this pull request introduce a breaking change?
Does this pull request introduce an observable change?
With this change,
customElements.get/customElement.whenDefinedchanges its behavior depending on whether or not a pivot (LWC) constructor was defined. But this only occurs if the flag is set, and we haven't enabled the flag anywhere yet.GUS work item
W-11884701