fix(animations): detect object trigger values with Object.hasOwn

StateValue and AnimationTransitionNamespace.trigger detect the {value,
params} object form of a trigger binding by calling hasOwnProperty on the
bound value. When that value is an object from untrusted data (for example
a parsed JSON payload) carrying an own hasOwnProperty key, the shadowed
property is called as a method and throws, breaking the animation flush.
Use Object.hasOwn for the check so a shadowing key no longer matters.
This commit is contained in:
arshiya tabasum
2026-08-11 18:25:30 +05:30
committed by Jessica Janiuk
parent 1a006a8f97
commit c73a001fbf
2 changed files with 21 additions and 2 deletions
@@ -112,7 +112,7 @@ class StateValue {
input: any,
public namespaceId: string = '',
) {
const isObj = input && input.hasOwnProperty('value');
const isObj = input && Object.hasOwn(input, 'value');
const value = isObj ? input['value'] : input;
this.value = normalizeTriggerValue(value);
if (isObj) {
@@ -246,7 +246,7 @@ class AnimationTransitionNamespace {
let fromState = triggersWithStates.get(triggerName);
const toState = new StateValue(value, this.id);
const isObj = value && value.hasOwnProperty('value');
const isObj = value && Object.hasOwn(value, 'value');
if (!isObj && fromState) {
toState.absorbOptions(fromState.options);
}
@@ -118,6 +118,25 @@ const DEFAULT_NAMESPACE_ID = 'id';
expect(engine.players.length).toEqual(1);
});
it('should read the value from a trigger object whose own key shadows hasOwnProperty', () => {
const engine = makeEngine();
const trig = trigger('myTrigger', [
transition('* => *', [style({height: '0px'}), animate(1000, style({height: '100px'}))]),
]);
registerTrigger(element, engine, trig);
// A bound trigger value in the `{value, params}` object form can come from
// untrusted data (e.g. a parsed JSON payload) and carry an own `hasOwnProperty`
// key that shadows the method. Detecting the object form must not depend on it.
const value = JSON.parse('{"value": "matched", "hasOwnProperty": "x"}');
expect(() => setProperty(element, engine, 'myTrigger', value)).not.toThrow();
engine.flush();
expect(engine.players.length).toEqual(1);
});
it('should throw an error if an animation property without a matching trigger is changed', () => {
const engine = makeEngine();
expect(() => {