diff --git a/packages/@lwc/engine-core/src/framework/invoker.ts b/packages/@lwc/engine-core/src/framework/invoker.ts index 724695c0f8..fa0877a514 100644 --- a/packages/@lwc/engine-core/src/framework/invoker.ts +++ b/packages/@lwc/engine-core/src/framework/invoker.ts @@ -11,9 +11,10 @@ import { addErrorComponentStack } from '../shared/error'; import { evaluateTemplate, setVMBeingRendered, getVMBeingRendered } from './template'; import { runWithBoundaryProtection } from './vm'; import { logOperationStart, logOperationEnd, OperationId } from './profiler'; +import { LightningElement } from './base-lightning-element'; import type { Template } from './template'; import type { VM } from './vm'; -import type { LightningElement, LightningElementConstructor } from './base-lightning-element'; +import type { LightningElementConstructor } from './base-lightning-element'; import type { VNodes } from './vnodes'; export let isInvokingRender: boolean = false; @@ -57,7 +58,11 @@ export function invokeComponentConstructor(vm: VM, Ctor: LightningElementConstru // the "instanceof" operator would not work here since Locker Service provides its own // implementation of LightningElement, so we indirectly check if the base constructor is // invoked by accessing the component on the vm. - if (vmBeingConstructed.component !== result) { + const isInvalidConstructor = lwcRuntimeFlags.ENABLE_LEGACY_LOCKER_SUPPORT + ? vmBeingConstructed.component !== result + : !(result instanceof LightningElement); + + if (isInvalidConstructor) { throw new TypeError( 'Invalid component constructor, the class should extend LightningElement.' ); diff --git a/packages/@lwc/features/src/index.ts b/packages/@lwc/features/src/index.ts index 4dd0bbedf8..d85e8276ca 100644 --- a/packages/@lwc/features/src/index.ts +++ b/packages/@lwc/features/src/index.ts @@ -20,6 +20,7 @@ const features: FeatureFlagMap = { ENABLE_FORCE_SHADOW_MIGRATE_MODE: null, ENABLE_EXPERIMENTAL_SIGNALS: null, DISABLE_SYNTHETIC_SHADOW: null, + ENABLE_LEGACY_LOCKER_SUPPORT: null, }; if (!(globalThis as any).lwcRuntimeFlags) { diff --git a/packages/@lwc/features/src/types.ts b/packages/@lwc/features/src/types.ts index 1a8cfd23f1..a2f148d73e 100644 --- a/packages/@lwc/features/src/types.ts +++ b/packages/@lwc/features/src/types.ts @@ -75,6 +75,12 @@ export interface FeatureFlagMap { * native shadow mode. */ DISABLE_SYNTHETIC_SHADOW: FeatureFlagValue; + + /** + * If true, then lightning legacy locker is supported, otherwise lightning legacy locker will not function + * properly. + */ + ENABLE_LEGACY_LOCKER_SUPPORT: FeatureFlagValue; } export type FeatureFlagName = keyof FeatureFlagMap; diff --git a/packages/@lwc/integration-karma/test/api/createElement/index.spec.js b/packages/@lwc/integration-karma/test/api/createElement/index.spec.js index e8137570e9..a9beeab9d6 100644 --- a/packages/@lwc/integration-karma/test/api/createElement/index.spec.js +++ b/packages/@lwc/integration-karma/test/api/createElement/index.spec.js @@ -1,4 +1,4 @@ -import { createElement, LightningElement } from 'lwc'; +import { createElement, LightningElement, setFeatureFlagForTest } from 'lwc'; import { isNativeShadowRootInstance, isSyntheticShadowRootInstance } from 'test-utils'; import Test from 'x/test'; @@ -97,6 +97,12 @@ describe.runIf(process.env.NATIVE_SHADOW)('native shadow', () => { }); describe('locker integration', () => { + beforeEach(() => { + setFeatureFlagForTest('ENABLE_LEGACY_LOCKER_SUPPORT', true); + }); + afterEach(() => { + setFeatureFlagForTest('ENABLE_LEGACY_LOCKER_SUPPORT', false); + }); it('should support component class that extend a mirror of the LightningElement', () => { function SecureBaseClass() { if (this instanceof SecureBaseClass) { diff --git a/packages/@lwc/integration-karma/test/component/LightningElement/index.spec.js b/packages/@lwc/integration-karma/test/component/LightningElement/index.spec.js index fe204b0563..7f396c86bd 100644 --- a/packages/@lwc/integration-karma/test/component/LightningElement/index.spec.js +++ b/packages/@lwc/integration-karma/test/component/LightningElement/index.spec.js @@ -5,6 +5,7 @@ import NotReturningThis from 'x/notReturningThis'; import ParentThrowingBeforeSuper from 'x/parentThrowingBeforeSuper'; import DefinedComponent from 'x/definedComponent'; import UndefinedComponent from 'x/undefinedComponent'; +import ReturningBad from 'x/returningBad'; it('should throw when trying to invoke the constructor manually', () => { const func = () => { @@ -79,3 +80,12 @@ it("[W-6981076] shouldn't throw when a component with an invalid child in unmoun ); expect(() => document.body.removeChild(elm)).not.toThrow(); }); + +it('should fail when the constructor returns something other than an instance of itself', () => { + expect(() => { + createElement('x-returning-bad', { is: ReturningBad }); + }).toThrowError( + TypeError, + 'Invalid component constructor, the class should extend LightningElement.' + ); +}); diff --git a/packages/@lwc/integration-karma/test/component/LightningElement/x/returningBad/returningBad.js b/packages/@lwc/integration-karma/test/component/LightningElement/x/returningBad/returningBad.js new file mode 100644 index 0000000000..62d22e25e8 --- /dev/null +++ b/packages/@lwc/integration-karma/test/component/LightningElement/x/returningBad/returningBad.js @@ -0,0 +1,12 @@ +import { LightningElement } from 'lwc'; + +export default class MyClass extends LightningElement { + constructor() { + super(); + + const bad = {}; + LightningElement.call(bad); + + return bad; + } +} diff --git a/packages/@lwc/integration-karma/test/integrations/locker/index.spec.js b/packages/@lwc/integration-karma/test/integrations/locker/index.spec.js index 2a1783bedf..131135afac 100644 --- a/packages/@lwc/integration-karma/test/integrations/locker/index.spec.js +++ b/packages/@lwc/integration-karma/test/integrations/locker/index.spec.js @@ -1,9 +1,14 @@ -import { createElement } from 'lwc'; +import { createElement, setFeatureFlagForTest } from 'lwc'; import LockerIntegration from 'x/lockerIntegration'; import LockerLiveComponent from 'x/lockerLiveComponent'; import LockerHooks, { hooks } from 'x/lockerHooks'; - +beforeEach(() => { + setFeatureFlagForTest('ENABLE_LEGACY_LOCKER_SUPPORT', true); +}); +afterEach(() => { + setFeatureFlagForTest('ENABLE_LEGACY_LOCKER_SUPPORT', false); +}); it('should support Locker integration which uses a wrapped LightningElement base class', () => { const elm = createElement('x-secure-parent', { is: LockerIntegration }); document.body.appendChild(elm);