Fix bugs that occur when event responder unmounts during a touch event sequence
> This PR adds a test (initially failing) for this case along with a fix for stack and fiber. The stack fix was copied from a Diff submitted by @sema. The fiber fix just required us to stop leaking properties for unmounted views. > Longer term we may want to explicitly invoke a release event listener for a responder just before unmounting it. This PR does not do that.
This commit is contained in:
parent
42c375b136
commit
a44a5d68d8
|
@ -1454,6 +1454,7 @@ src/renderers/native/__tests__/ReactNativeAttributePayload-test.js
|
||||||
|
|
||||||
src/renderers/native/__tests__/ReactNativeEvents-test.js
|
src/renderers/native/__tests__/ReactNativeEvents-test.js
|
||||||
* handles events
|
* handles events
|
||||||
|
* handles when a responder is unmounted while a touch sequence is in progress
|
||||||
|
|
||||||
src/renderers/native/__tests__/ReactNativeMount-test.js
|
src/renderers/native/__tests__/ReactNativeMount-test.js
|
||||||
* should be able to create and render a native component
|
* should be able to create and render a native component
|
||||||
|
|
|
@ -12,7 +12,9 @@
|
||||||
// Mock of the Native Hooks
|
// Mock of the Native Hooks
|
||||||
|
|
||||||
var RCTUIManager = {
|
var RCTUIManager = {
|
||||||
|
clearJSResponder: jest.fn(),
|
||||||
createView: jest.fn(),
|
createView: jest.fn(),
|
||||||
|
setJSResponder: jest.fn(),
|
||||||
setChildren: jest.fn(),
|
setChildren: jest.fn(),
|
||||||
manageChildren: jest.fn(),
|
manageChildren: jest.fn(),
|
||||||
updateView: jest.fn(),
|
updateView: jest.fn(),
|
||||||
|
|
|
@ -53,6 +53,7 @@ function uncacheNode(inst) {
|
||||||
|
|
||||||
function uncacheFiberNode(tag) {
|
function uncacheFiberNode(tag) {
|
||||||
delete instanceCache[tag];
|
delete instanceCache[tag];
|
||||||
|
delete instanceProps[tag];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInstanceFromTag(tag) {
|
function getInstanceFromTag(tag) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ var RCTEventEmitter;
|
||||||
var React;
|
var React;
|
||||||
var ReactErrorUtils;
|
var ReactErrorUtils;
|
||||||
var ReactNative;
|
var ReactNative;
|
||||||
|
var ResponderEventPlugin;
|
||||||
var UIManager;
|
var UIManager;
|
||||||
var createReactNativeComponentClass;
|
var createReactNativeComponentClass;
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ beforeEach(() => {
|
||||||
React = require('React');
|
React = require('React');
|
||||||
ReactErrorUtils = require('ReactErrorUtils');
|
ReactErrorUtils = require('ReactErrorUtils');
|
||||||
ReactNative = require('ReactNative');
|
ReactNative = require('ReactNative');
|
||||||
|
ResponderEventPlugin = require('ResponderEventPlugin');
|
||||||
UIManager = require('UIManager');
|
UIManager = require('UIManager');
|
||||||
createReactNativeComponentClass = require('createReactNativeComponentClass');
|
createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||||
|
|
||||||
|
@ -91,3 +93,97 @@ it('handles events', () => {
|
||||||
'outer touchend',
|
'outer touchend',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles when a responder is unmounted while a touch sequence is in progress', () => {
|
||||||
|
var EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
|
||||||
|
var View = createReactNativeComponentClass({
|
||||||
|
validAttributes: { id: true },
|
||||||
|
uiViewClassName: 'View',
|
||||||
|
});
|
||||||
|
|
||||||
|
function getViewById(id) {
|
||||||
|
return UIManager.createView.mock.calls.find(
|
||||||
|
args => args[3] && args[3].id === id
|
||||||
|
)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResponderId() {
|
||||||
|
const responder = ResponderEventPlugin._getResponder();
|
||||||
|
if (responder === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const props = typeof responder.tag === 'number'
|
||||||
|
? responder.memoizedProps
|
||||||
|
: responder._currentElement.props;
|
||||||
|
return props ? props.id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var log = [];
|
||||||
|
ReactNative.render(
|
||||||
|
<View id="parent">
|
||||||
|
<View key={1}>
|
||||||
|
<View
|
||||||
|
id="one"
|
||||||
|
onResponderEnd={() => log.push('one responder end')}
|
||||||
|
onResponderStart={() => log.push('one responder start')}
|
||||||
|
onStartShouldSetResponder={() => true}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View key={2}>
|
||||||
|
<View
|
||||||
|
id="two"
|
||||||
|
onResponderEnd={() => log.push('two responder end')}
|
||||||
|
onResponderStart={() => log.push('two responder start')}
|
||||||
|
onStartShouldSetResponder={() => true}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
EventEmitter.receiveTouches(
|
||||||
|
'topTouchStart',
|
||||||
|
[{target: getViewById('one'), identifier: 17}],
|
||||||
|
[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getResponderId()).toBe('one');
|
||||||
|
expect(log).toEqual(['one responder start']);
|
||||||
|
log.splice(0);
|
||||||
|
|
||||||
|
ReactNative.render(
|
||||||
|
<View id="parent">
|
||||||
|
<View key={2}>
|
||||||
|
<View
|
||||||
|
id="two"
|
||||||
|
onResponderEnd={() => log.push('two responder end')}
|
||||||
|
onResponderStart={() => log.push('two responder start')}
|
||||||
|
onStartShouldSetResponder={() => true}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO Verify the onResponderEnd listener has been called (before the unmount)
|
||||||
|
// expect(log).toEqual(['one responder end']);
|
||||||
|
// log.splice(0);
|
||||||
|
|
||||||
|
EventEmitter.receiveTouches(
|
||||||
|
'topTouchEnd',
|
||||||
|
[{target: getViewById('two'), identifier: 17}],
|
||||||
|
[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getResponderId()).toBeNull();
|
||||||
|
expect(log).toEqual([]);
|
||||||
|
|
||||||
|
EventEmitter.receiveTouches(
|
||||||
|
'topTouchStart',
|
||||||
|
[{target: getViewById('two'), identifier: 17}],
|
||||||
|
[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getResponderId()).toBe('two');
|
||||||
|
expect(log).toEqual(['two responder start']);
|
||||||
|
});
|
||||||
|
|
|
@ -142,6 +142,10 @@ var EventPluginHub = {
|
||||||
// Text node, let it bubble through.
|
// Text node, let it bubble through.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (!inst._rootNodeID) {
|
||||||
|
// If the instance is already unmounted, we have no listeners.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const props = inst._currentElement.props;
|
const props = inst._currentElement.props;
|
||||||
listener = props[registrationName];
|
listener = props[registrationName];
|
||||||
if (shouldPreventMouseEvent(registrationName, inst._currentElement.type, props)) {
|
if (shouldPreventMouseEvent(registrationName, inst._currentElement.type, props)) {
|
||||||
|
|
Loading…
Reference in New Issue