Skip to content

Commit fcb0237

Browse files
chore: re-introduce new constructor validation with gate (#5320)
* chore: re-introduce new constructor validation with gate * fix: constructor type * fix: reversed flag * fix: flag logic * fix: flag logic, test * fix: gate naming * fix: added extra test to please will
1 parent a221660 commit fcb0237

File tree

4 files changed

+48
-13
lines changed

4 files changed

+48
-13
lines changed

packages/@lwc/engine-core/src/framework/invoker.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { addErrorComponentStack } from '../shared/error';
1111
import { evaluateTemplate, setVMBeingRendered, getVMBeingRendered } from './template';
1212
import { runWithBoundaryProtection } from './vm';
1313
import { logOperationStart, logOperationEnd, OperationId } from './profiler';
14-
import type { LightningElement } from './base-lightning-element';
14+
import { LightningElement } from './base-lightning-element';
1515
import type { Template } from './template';
1616
import type { VM } from './vm';
1717
import type { LightningElementConstructor } from './base-lightning-element';
@@ -54,16 +54,18 @@ export function invokeComponentConstructor(vm: VM, Ctor: LightningElementConstru
5454
// job
5555
const result = new Ctor();
5656

57-
// Check indirectly if the constructor result is an instance of LightningElement. Using
58-
// the "instanceof" operator would not work here since Locker Service provides its own
59-
// implementation of LightningElement, so we indirectly check if the base constructor is
60-
// invoked by accessing the component on the vm.
57+
// Check indirectly if the constructor result is an instance of LightningElement.
58+
// When Locker is enabled, the "instanceof" operator would not work since Locker Service
59+
// provides its own implementation of LightningElement, so we indirectly check
60+
// if the base constructor is invoked by accessing the component on the vm.
61+
// When the ENABLE_LOCKER_VALIDATION gate is true and LEGACY_LOCKER_ENABLED is false,
62+
// then the instanceof LightningElement can be used.
63+
const useLegacyConstructorCheck =
64+
lwcRuntimeFlags.ENABLE_LEGACY_VALIDATION || lwcRuntimeFlags.LEGACY_LOCKER_ENABLED;
6165

62-
// TODO [W-17769475]: Restore this fix when we can reliably detect Locker enabled
63-
// const isInvalidConstructor = lwcRuntimeFlags.LEGACY_LOCKER_ENABLED
64-
// ? vmBeingConstructed.component !== result
65-
// : !(result instanceof LightningElement);
66-
const isInvalidConstructor = vmBeingConstructed.component !== result;
66+
const isInvalidConstructor = useLegacyConstructorCheck
67+
? vmBeingConstructed.component !== result
68+
: !(result instanceof LightningElement);
6769

6870
if (isInvalidConstructor) {
6971
throw new TypeError(

packages/@lwc/features/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const features: FeatureFlagMap = {
2222
DISABLE_SYNTHETIC_SHADOW: null,
2323
DISABLE_SCOPE_TOKEN_VALIDATION: null,
2424
LEGACY_LOCKER_ENABLED: null,
25+
ENABLE_LEGACY_VALIDATION: null,
2526
};
2627

2728
if (!(globalThis as any).lwcRuntimeFlags) {

packages/@lwc/features/src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ export interface FeatureFlagMap {
8686
* properly.
8787
*/
8888
LEGACY_LOCKER_ENABLED: FeatureFlagValue;
89+
90+
/**
91+
* A manual override for `LEGACY_LOCKER_ENABLED`; should not be used if that flag is correctly set.
92+
* If true, behave as if legacy Locker is enabled.
93+
* If false or unset, then the value of the `LEGACY_LOCKER_ENABLED` flag is used.
94+
*/
95+
ENABLE_LEGACY_VALIDATION: FeatureFlagValue;
8996
}
9097

9198
export type FeatureFlagName = keyof FeatureFlagMap;

packages/@lwc/integration-karma/test/component/LightningElement/index.spec.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LightningElement, createElement } from 'lwc';
1+
import { LightningElement, createElement, setFeatureFlagForTest } from 'lwc';
22

33
import NotInvokingSuper from 'x/notInvokingSuper';
44
import NotReturningThis from 'x/notReturningThis';
@@ -81,12 +81,37 @@ it("[W-6981076] shouldn't throw when a component with an invalid child in unmoun
8181
expect(() => document.body.removeChild(elm)).not.toThrow();
8282
});
8383

84-
// TODO [W-17769475]: Restore this test when we can reliably detect Locker enabled
85-
xit('should fail when the constructor returns something other than an instance of itself', () => {
84+
it('should fail when the constructor returns something other than LightningElement when ENABLE_LEGACY_VALIDATION is falsy and LEGACY_LOCKER_ENABLED is falsy', () => {
8685
expect(() => {
8786
createElement('x-returning-bad', { is: ReturningBad });
8887
}).toThrowError(
8988
TypeError,
9089
'Invalid component constructor, the class should extend LightningElement.'
9190
);
9291
});
92+
93+
it('should succeed when the constructor returns something other than LightningElement when ENABLE_LEGACY_VALIDATION is true and LEGACY_LOCKER_ENABLED is falsy', () => {
94+
setFeatureFlagForTest('ENABLE_LEGACY_VALIDATION', true);
95+
expect(() => {
96+
createElement('x-returning-bad', { is: ReturningBad });
97+
}).not.toThrow();
98+
setFeatureFlagForTest('ENABLE_LEGACY_VALIDATION', false);
99+
});
100+
101+
it('should succeed when the constructor returns something other than LightningElement when ENABLE_LEGACY_VALIDATION is falsy and LEGACY_LOCKER_ENABLED is true', () => {
102+
setFeatureFlagForTest('LEGACY_LOCKER_ENABLED', true);
103+
expect(() => {
104+
createElement('x-returning-bad', { is: ReturningBad });
105+
}).not.toThrow();
106+
setFeatureFlagForTest('LEGACY_LOCKER_ENABLED', false);
107+
});
108+
109+
it('should succeed when the constructor returns something other than LightningElement when ENABLE_LEGACY_VALIDATION is falsy and LEGACY_LOCKER_ENABLED is true', () => {
110+
setFeatureFlagForTest('ENABLE_LEGACY_VALIDATION', true);
111+
setFeatureFlagForTest('LEGACY_LOCKER_ENABLED', true);
112+
expect(() => {
113+
createElement('x-returning-bad', { is: ReturningBad });
114+
}).not.toThrow();
115+
setFeatureFlagForTest('ENABLE_LEGACY_VALIDATION', false);
116+
setFeatureFlagForTest('LEGACY_LOCKER_ENABLED', false);
117+
});

0 commit comments

Comments
 (0)