Skip to content

Commit 19362f6

Browse files
javachefacebook-github-bot
authored andcommitted
Fix validation of AnimatedEvent mapping
Summary: Noticed the _validateMapping call right now is a no-op, as the traverse method is never invoked. We can only really do validation once we've received a sample of an event, and can then verify the values being extracted indeed correspond with a valid key. Changelog: [General] [Fixed] - Fix validation of event mappings for AnimatedEvent Reviewed By: cpojer Differential Revision: D19498971 fbshipit-source-id: e978dda895498a7e567d5e18b3181b319d88d95c
1 parent 26d5faf commit 19362f6

File tree

3 files changed

+80
-50
lines changed

3 files changed

+80
-50
lines changed

Libraries/Animated/src/AnimatedEvent.js

+74-47
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export type EventConfig = {
2828
function attachNativeEvent(
2929
viewRef: any,
3030
eventName: string,
31-
argMapping: Array<?Mapping>,
32-
): {|detach: () => void|} {
31+
argMapping: $ReadOnlyArray<?Mapping>,
32+
): {detach: () => void} {
3333
// Find animated values in `argMapping` and create an array representing their
3434
// key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x'].
3535
const eventMappings = [];
@@ -58,7 +58,6 @@ function attachNativeEvent(
5858
traverse(argMapping[0].nativeEvent, []);
5959

6060
const viewTag = ReactNative.findNodeHandle(viewRef);
61-
6261
if (viewTag != null) {
6362
eventMappings.forEach(mapping => {
6463
NativeAnimatedHelper.API.addAnimatedEventToView(
@@ -84,14 +83,59 @@ function attachNativeEvent(
8483
};
8584
}
8685

86+
function validateMapping(argMapping, args) {
87+
const validate = (recMapping, recEvt, key) => {
88+
if (recMapping instanceof AnimatedValue) {
89+
invariant(
90+
typeof recEvt === 'number',
91+
'Bad mapping of event key ' +
92+
key +
93+
', should be number but got ' +
94+
typeof recEvt,
95+
);
96+
return;
97+
}
98+
if (typeof recEvt === 'number') {
99+
invariant(
100+
recMapping instanceof AnimatedValue,
101+
'Bad mapping of type ' +
102+
typeof recMapping +
103+
' for key ' +
104+
key +
105+
', event value must map to AnimatedValue',
106+
);
107+
return;
108+
}
109+
invariant(
110+
typeof recMapping === 'object',
111+
'Bad mapping of type ' + typeof recMapping + ' for key ' + key,
112+
);
113+
invariant(
114+
typeof recEvt === 'object',
115+
'Bad event of type ' + typeof recEvt + ' for key ' + key,
116+
);
117+
for (const mappingKey in recMapping) {
118+
validate(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
119+
}
120+
};
121+
122+
invariant(
123+
args.length >= argMapping.length,
124+
'Event has less arguments than mapping',
125+
);
126+
argMapping.forEach((mapping, idx) => {
127+
validate(mapping, args[idx], 'arg' + idx);
128+
});
129+
}
130+
87131
class AnimatedEvent {
88-
_argMapping: Array<?Mapping>;
132+
_argMapping: $ReadOnlyArray<?Mapping>;
89133
_listeners: Array<Function> = [];
90134
_callListeners: Function;
91135
_attachedEvent: ?{detach: () => void, ...};
92136
__isNative: boolean;
93137

94-
constructor(argMapping: Array<?Mapping>, config: EventConfig) {
138+
constructor(argMapping: $ReadOnlyArray<?Mapping>, config: EventConfig) {
95139
this._argMapping = argMapping;
96140

97141
if (config == null) {
@@ -105,10 +149,6 @@ class AnimatedEvent {
105149
this._callListeners = this._callListeners.bind(this);
106150
this._attachedEvent = null;
107151
this.__isNative = shouldUseNativeDriver(config);
108-
109-
if (__DEV__) {
110-
this._validateMapping();
111-
}
112152
}
113153

114154
__addListener(callback: Function): void {
@@ -143,62 +183,49 @@ class AnimatedEvent {
143183

144184
__getHandler(): any | ((...args: any) => void) {
145185
if (this.__isNative) {
146-
return this._callListeners;
186+
if (__DEV__) {
187+
let validatedMapping = false;
188+
return (...args: any) => {
189+
if (!validatedMapping) {
190+
validateMapping(this._argMapping, args);
191+
validatedMapping = true;
192+
}
193+
this._callListeners(...args);
194+
};
195+
} else {
196+
return this._callListeners;
197+
}
147198
}
148199

200+
let validatedMapping = false;
149201
return (...args: any) => {
202+
if (__DEV__ && !validatedMapping) {
203+
validateMapping(this._argMapping, args);
204+
validatedMapping = true;
205+
}
206+
150207
const traverse = (recMapping, recEvt, key) => {
151-
if (typeof recEvt === 'number' && recMapping instanceof AnimatedValue) {
152-
recMapping.setValue(recEvt);
208+
if (recMapping instanceof AnimatedValue) {
209+
if (typeof recEvt === 'number') {
210+
recMapping.setValue(recEvt);
211+
}
153212
} else if (typeof recMapping === 'object') {
154213
for (const mappingKey in recMapping) {
155-
/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This
156-
* comment suppresses an error when upgrading Flow's support for
157-
* React. To see the error delete this comment and run Flow. */
158214
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
159215
}
160216
}
161217
};
218+
this._argMapping.forEach((mapping, idx) => {
219+
traverse(mapping, args[idx], 'arg' + idx);
220+
});
162221

163-
if (!this.__isNative) {
164-
this._argMapping.forEach((mapping, idx) => {
165-
traverse(mapping, args[idx], 'arg' + idx);
166-
});
167-
}
168222
this._callListeners(...args);
169223
};
170224
}
171225

172226
_callListeners(...args: any) {
173227
this._listeners.forEach(listener => listener(...args));
174228
}
175-
176-
_validateMapping() {
177-
const traverse = (recMapping, recEvt, key) => {
178-
if (typeof recEvt === 'number') {
179-
invariant(
180-
recMapping instanceof AnimatedValue,
181-
'Bad mapping of type ' +
182-
typeof recMapping +
183-
' for key ' +
184-
key +
185-
', event value must map to AnimatedValue',
186-
);
187-
return;
188-
}
189-
invariant(
190-
typeof recMapping === 'object',
191-
'Bad mapping of type ' + typeof recMapping + ' for key ' + key,
192-
);
193-
invariant(
194-
typeof recEvt === 'object',
195-
'Bad event of type ' + typeof recEvt + ' for key ' + key,
196-
);
197-
for (const mappingKey in recMapping) {
198-
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
199-
}
200-
};
201-
}
202229
}
203230

204231
module.exports = {AnimatedEvent, attachNativeEvent};

Libraries/Animated/src/AnimatedImplementation.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,10 @@ function unforkEvent(
522522
}
523523
}
524524

525-
const event = function(argMapping: Array<?Mapping>, config: EventConfig): any {
525+
const event = function(
526+
argMapping: $ReadOnlyArray<?Mapping>,
527+
config: EventConfig,
528+
): any {
526529
const animatedEvent = new AnimatedEvent(argMapping, config);
527530
if (animatedEvent.__isNative) {
528531
return animatedEvent;

Libraries/Animated/src/__tests__/AnimatedNative-test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,9 @@ describe('Native Animated', () => {
260260
listener,
261261
});
262262
const handler = event.__getHandler();
263-
handler({foo: 42});
263+
handler({nativeEvent: {foo: 42}});
264264
expect(listener).toHaveBeenCalledTimes(1);
265-
expect(listener).toBeCalledWith({foo: 42});
265+
expect(listener).toBeCalledWith({nativeEvent: {foo: 42}});
266266
});
267267
});
268268

0 commit comments

Comments
 (0)