Skip to content

Commit

Permalink
fix: @W-17528759 - add flag to control locker integration (#5136)
Browse files Browse the repository at this point in the history
* mob: repro of constructor hack

* feat: added legacy locker flag

* fix: added locker flag to tests

---------

Co-authored-by: Nolan Lawson <[email protected]>
  • Loading branch information
jhefferman-sfdc and nolanlawson authored Jan 21, 2025
1 parent bcd6391 commit 0cbf753
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 5 deletions.
9 changes: 7 additions & 2 deletions packages/@lwc/engine-core/src/framework/invoker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.'
);
Expand Down
1 change: 1 addition & 0 deletions packages/@lwc/features/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions packages/@lwc/features/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -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.'
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { LightningElement } from 'lwc';

export default class MyClass extends LightningElement {
constructor() {
super();

const bad = {};
LightningElement.call(bad);

return bad;
}
}
Original file line number Diff line number Diff line change
@@ -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);
Expand Down

0 comments on commit 0cbf753

Please sign in to comment.