isTrustedSignal/Context should return false when no trusted set has been defined#5492
isTrustedSignal/Context should return false when no trusted set has been defined#5492jhefferman-sfdc wants to merge 9 commits intomasterfrom
Conversation
Pretty sure changing "this returns Also, why? |
Explain what makes you sure of this?
Please see my thorough explanation in the description (p.s. thank you for the inspiration) |
|
If an OSS user wants to use alternative signal implementations with LWC, they can currently do that by simply not calling If we do care about it, my proposal is to update the function signature to As part of integrating signals into the framework, we added the concept of "trusted" signals in #4665. This restricted the framework to only use signals from lwc/packages/@lwc/engine-core/src/framework/mutation-tracker.ts Lines 45 to 58 in f2583dc Later, a performance regression related to the lwc/packages/@lwc/engine-core/src/framework/mutation-tracker.ts Lines 40 to 50 in 98a4d69 However, this had an unintended consequence. If lwc/packages/@lwc/engine-core/src/libs/signal-tracker/index.ts Lines 60 to 75 in 9e4d3fa Ideally, in order to only subscribe to actual signals, we would check the shape of the mutated object. Unfortunately, due to performance issues, that is not a viable approach. An alternative is to change the behavior of the "trusted" mechanism. Everything works correctly when Even though this is technically a breaking change, the signals feature is considered a subset of state management, which is not yet released. Additionally, we don't know of any users that are using non-trusted signals with LWC. Therefore, this seems like a reasonably safe breaking change to make. |
|
This is a pretty minor thought on a feature that's already shipped, so it's probably not worth doing. 😞 import * as lwc from 'lwc'
import * as lwcSignals from '@lwc/signals'
const signals = new WeakMap()
lwc.setTrustedSignalSet(signals)
// ^? function setTrustedSignalSet(signals: WeakSet<object>): void;
lwcSignals.setTrustedSignalSet(signals)
// ^? function setTrustedSignalSet(signals: WeakSet<object>): void;While we're at it, the name import * as lwc from 'lwc'
import * as lwcSignals from 'lwcSignals'
lwc.enableSignals( // => function enableSignals(signalTracker: WeakMap<object>): void;
lwcSignals.createSignalTracker() // => function createSignalTracker(): WeakMap<object>;
)Something like this feels a lot more ergonomic. The goal is clear (enable signals). The name "signal tracker" doesn't explain everything, but it's gives more of an idea than "trusted signal set". And with the changed signature, it's not quite entirely hidden, but the user doesn't have to create it or manage it on their own. However, if they want to, they still can! |
|
@jhefferman-sfdc do you remember if |
wjhsf
left a comment
There was a problem hiding this comment.
Can you add some tests that validate that nothing is logged to console when a non-signal object is mutation tracked?
| { | ||
| "path": "packages/@lwc/engine-dom/dist/index.js", | ||
| "maxSize": "24.68KB" | ||
| "maxSize": "24.72KB" |
| (lwcRuntimeFlags.ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION && | ||
| legacyIsTrustedSignal(target)) || | ||
| (!lwcRuntimeFlags.ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION && isTrustedSignal(target)) |
There was a problem hiding this comment.
| (lwcRuntimeFlags.ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION && | |
| legacyIsTrustedSignal(target)) || | |
| (!lwcRuntimeFlags.ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION && isTrustedSignal(target)) | |
| lwcRuntimeFlags.ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION && | |
| ? legacyIsTrustedSignal(target) | |
| : isTrustedSignal(target) |
bruv
There was a problem hiding this comment.
I had a slight preference for the verbose syntax but on second thoughts, you're right
| lwcRuntimeFlags.ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION | ||
| ? legacyIsTrustedContext((component as any)[enumerableKey]) | ||
| : isTrustedContext((component as any)[enumerableKey]) | ||
| ); |
There was a problem hiding this comment.
@jhefferman-sfdc - Just to confirm, this is how it will work with the killswitch right?
killswitch on => ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION is off
killswitch off => ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION is on
There was a problem hiding this comment.
@jmsjtu ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION is the kill switch so:
killswitch on => ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION is on => use current legacy validation which were are fixing (legacyIsTrustedSignal/Context)
killswitch off => ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION is off => use corrected validation (isTrustedSignal/Context)
Is that OK / makes sense?
There was a problem hiding this comment.
@jmsjtu you are correct, but I think we will have to invert the flag in core as our default flag state is undefined and we want the default behavior to be the corrected behavior? Aka what you said:
killswitch on => ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION is off
killswitch off => ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION is on
| it('should return false for all calls when trustedContexts is not set', () => { | ||
| expect(isTrustedContext({})).toBe(false); | ||
| }); | ||
|
|
||
| it('legacyIsTrustedContext should return true when trustedContexts is not set', () => { | ||
| expect(legacyIsTrustedContext({})).toBe(true); |
There was a problem hiding this comment.
For posterity, can we add a test that shows the legacy vs new beavior?
Ex, a test where the flag is enabled vs disabled and the expected outputs.
There was a problem hiding this comment.
Good idea, I've added some tests around this change.
packages/@lwc/shared/src/context.ts
Outdated
| /** | ||
| * The legacy validation behavior was that this check should only | ||
| * be performed for runtimes that have provided a trustedContext set. | ||
| * However, this resulted in a bug as all component properties were |
There was a problem hiding this comment.
nit: technically it was only object values, not all values
|
@wjhsf :
Interesting concept but I'd rather not rock the boat at this stage. Gonna stick with Dale's proposed WeakSet approach.
It does seem clearer but it will change the contract with the state managers and they've already baked this into their codebase. Perhaps we can review if we do another round of improvements with them.
Thanks for your problem summary. However the main reasoning you mention here appears to be an edge case because:
|
|
/nucleus ignore -m "unrelated failures" |
| vi.mock('../../shared/logger', () => ({ | ||
| logWarnOnce: vi.fn(), | ||
| })); |
There was a problem hiding this comment.
We have some helper methods defined already for the loggers you could use.
| const setFeatureFlag = (name: string, value: boolean) => { | ||
| (globalThis as any).lwcRuntimeFlags[name] = value; | ||
| }; |
There was a problem hiding this comment.
You can re-use the setFeatureFlagsForTest helper instead of defining your own.
| describe('without setting trusted context', () => { | ||
| it('should log a warning when trustedContext is not defined and connectContext is called with legacy signal context validation', () => { | ||
| setFeatureFlag('ENABLE_LEGACY_SIGNAL_CONTEXT_VALIDATION', true); | ||
| connectContext(mockVM); | ||
| expect(logWarnOnce).toHaveBeenCalledWith( | ||
| 'Attempted to connect to trusted context but received the following error: component[contextfulKeys[i]][connectContext2] is not a function' | ||
| ); | ||
| }); |
There was a problem hiding this comment.
Instead of creating these two tests in context/mutation-tracker how would you feel if we created a single test using an actual LWC to replicate the before and after behavior?
Is it possible to toggle the trusted signal set in that way?
Details
An internal Signals API is not being called in this context, as state managers are not in use. When the internal API is not called, all objects are considered signals and a warning is logged for properties that do not meet the criteria.
Does this pull request introduce a breaking change?
Does this pull request introduce an observable change?
GUS work item
W-19640386