@samitouri / QOS-React / commits / 18083a8a73

Revert "Remove old IE polyfill code" (#10483)

**what is the change?:** This reverts https://github.com/facebook/react/commit/0b220d0f04c9fea472c7cf8a7837322388728ca6 Which was part of https://github.com/facebook/react/pull/10238 **why make this change?:** When trying to sync the latest React with FB's codebase, we had failing integration tests. It looks like they are running with an old version of Chrome and there is something related to file upload that fails when these polyfills are missing. For now we'd like to revert this to unblock syncing, but it's worth revisiting this change to try and add some polyfill for FB and remove it from React, or to fix whatever the specific issue was with our file upload error. **test plan:** `yarn test` and also built and played with the `dom` and `packaging` fixtures

Flarnie Marchan committed Aug 17, 2017 at 16:19 UTC 18083a8a7305483e219592914ef52729907a4368
2 files changed +177 -47
src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js
+167 -42
@@ -11,14 +11,17 @@
11
12 'use strict';
13
14 +var EventPluginHub = require('EventPluginHub');
15 var EventPropagators = require('EventPropagators');
16 var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
17 var ReactControlledComponent = require('ReactControlledComponent');
18 var ReactDOMComponentTree = require('ReactDOMComponentTree');
19 +var ReactGenericBatching = require('ReactGenericBatching');
20 var SyntheticEvent = require('SyntheticEvent');
19 -var getActiveElement = require('fbjs/lib/getActiveElement');
21
22 var inputValueTracking = require('inputValueTracking');
23 +var getEventTarget = require('getEventTarget');
24 +var isEventSupported = require('isEventSupported');
25 var isTextInputElement = require('isTextInputElement');
26
27 var eventTypes = {
@@ -65,53 +68,174 @@ function createAndAccumulateChangeEvent(
68 EventPropagators.accumulateTwoPhaseDispatches(event);
69 return event;
70 }
71 +/**
72 + * For IE shims
73 + */
74 +var activeElement = null;
75 +var activeElementInst = null;
76
69 -function getInstIfValueChanged(targetInst, targetNode) {
77 +/**
78 + * SECTION: handle `change` event
79 + */
80 +function shouldUseChangeEvent(elem) {
81 + var nodeName = elem.nodeName && elem.nodeName.toLowerCase();
82 + return (
83 + nodeName === 'select' || (nodeName === 'input' && elem.type === 'file')
84 + );
85 +}
86 +
87 +function manualDispatchChangeEvent(nativeEvent) {
88 + var event = createAndAccumulateChangeEvent(
89 + activeElementInst,
90 + nativeEvent,
91 + getEventTarget(nativeEvent),
92 + );
93 +
94 + // If change and propertychange bubbled, we'd just bind to it like all the
95 + // other events and have it go through ReactBrowserEventEmitter. Since it
96 + // doesn't, we manually listen for the events and so we have to enqueue and
97 + // process the abstract event manually.
98 + //
99 + // Batching is necessary here in order to ensure that all event handlers run
100 + // before the next rerender (including event handlers attached to ancestor
101 + // elements instead of directly on the input). Without this, controlled
102 + // components don't work properly in conjunction with event bubbling because
103 + // the component is rerendered and the value reverted before all the event
104 + // handlers can run. See https://github.com/facebook/react/issues/708.
105 + ReactGenericBatching.batchedUpdates(runEventInBatch, event);
106 +}
107 +
108 +function runEventInBatch(event) {
109 + EventPluginHub.enqueueEvents(event);
110 + EventPluginHub.processEventQueue(false);
111 +}
112 +
113 +function getInstIfValueChanged(targetInst) {
114 + const targetNode = ReactDOMComponentTree.getNodeFromInstance(targetInst);
115 if (inputValueTracking.updateValueIfChanged(targetNode)) {
116 return targetInst;
117 }
118 }
119
120 +function getTargetInstForChangeEvent(topLevelType, targetInst) {
121 + if (topLevelType === 'topChange') {
122 + return targetInst;
123 + }
124 +}
125 +
126 /**
127 * SECTION: handle `input` event
128 */
78 -
79 -var isTextInputEventSupported = false;
129 +var isInputEventSupported = false;
130 if (ExecutionEnvironment.canUseDOM) {
81 - isTextInputEventSupported =
82 - !document.documentMode || document.documentMode > 9;
131 + // IE9 claims to support the input event but fails to trigger it when
132 + // deleting text, so we ignore its input events.
133 + isInputEventSupported =
134 + isEventSupported('input') &&
135 + (!document.documentMode || document.documentMode > 9);
136 }
137
85 -function getTargetInstForInputEventPolyfill(
86 - topLevelType,
87 - targetInst,
88 - targetNode,
89 -) {
138 +/**
139 + * (For IE <=9) Starts tracking propertychange events on the passed-in element
140 + * and override the value property so that we can distinguish user events from
141 + * value changes in JS.
142 + */
143 +function startWatchingForValueChange(target, targetInst) {
144 + activeElement = target;
145 + activeElementInst = targetInst;
146 + activeElement.attachEvent('onpropertychange', handlePropertyChange);
147 +}
148 +
149 +/**
150 + * (For IE <=9) Removes the event listeners from the currently-tracked element,
151 + * if any exists.
152 + */
153 +function stopWatchingForValueChange() {
154 + if (!activeElement) {
155 + return;
156 + }
157 + activeElement.detachEvent('onpropertychange', handlePropertyChange);
158 + activeElement = null;
159 + activeElementInst = null;
160 +}
161 +
162 +/**
163 + * (For IE <=9) Handles a propertychange event, sending a `change` event if
164 + * the value of the active element has changed.
165 + */
166 +function handlePropertyChange(nativeEvent) {
167 + if (nativeEvent.propertyName !== 'value') {
168 + return;
169 + }
170 + if (getInstIfValueChanged(activeElementInst)) {
171 + manualDispatchChangeEvent(nativeEvent);
172 + }
173 +}
174 +
175 +function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
176 + if (topLevelType === 'topFocus') {
177 + // In IE9, propertychange fires for most input events but is buggy and
178 + // doesn't fire when text is deleted, but conveniently, selectionchange
179 + // appears to fire in all of the remaining cases so we catch those and
180 + // forward the event if the value has changed
181 + // In either case, we don't want to call the event handler if the value
182 + // is changed from JS so we redefine a setter for `.value` that updates
183 + // our activeElementValue variable, allowing us to ignore those changes
184 + //
185 + // stopWatching() should be a noop here but we call it just in case we
186 + // missed a blur event somehow.
187 + stopWatchingForValueChange();
188 + startWatchingForValueChange(target, targetInst);
189 + } else if (topLevelType === 'topBlur') {
190 + stopWatchingForValueChange();
191 + }
192 +}
193 +
194 +// For IE8 and IE9.
195 +function getTargetInstForInputEventPolyfill(topLevelType, targetInst) {
196 if (
91 - topLevelType === 'topInput' ||
92 - topLevelType === 'topChange' ||
93 - // These events catch anything the IE9 onInput misses
197 topLevelType === 'topSelectionChange' ||
198 topLevelType === 'topKeyUp' ||
199 topLevelType === 'topKeyDown'
200 ) {
98 - return getInstIfValueChanged(targetInst, targetNode);
201 + // On the selectionchange event, the target is just document which isn't
202 + // helpful for us so just check activeElement instead.
203 + //
204 + // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
205 + // propertychange on the first input event after setting `value` from a
206 + // script and fires only keydown, keypress, keyup. Catching keyup usually
207 + // gets it and catching keydown lets us fire an event for the first
208 + // keystroke if user does a key repeat (it'll be a little delayed: right
209 + // before the second keystroke). Other input methods (e.g., paste) seem to
210 + // fire selectionchange normally.
211 + return getInstIfValueChanged(activeElementInst);
212 }
213 }
214
102 -function getTargetInstForInputOrChangeEvent(
103 - topLevelType,
104 - targetInst,
105 - targetNode,
106 -) {
107 - if (topLevelType === 'topInput' || topLevelType === 'topChange') {
108 - return getInstIfValueChanged(targetInst, targetNode);
215 +/**
216 + * SECTION: handle `click` event
217 + */
218 +function shouldUseClickEvent(elem) {
219 + // Use the `click` event to detect changes to checkbox and radio inputs.
220 + // This approach works across all browsers, whereas `change` does not fire
221 + // until `blur` in IE8.
222 + var nodeName = elem.nodeName;
223 + return (
224 + nodeName &&
225 + nodeName.toLowerCase() === 'input' &&
226 + (elem.type === 'checkbox' || elem.type === 'radio')
227 + );
228 +}
229 +
230 +function getTargetInstForClickEvent(topLevelType, targetInst) {
231 + if (topLevelType === 'topClick') {
232 + return getInstIfValueChanged(targetInst);
233 }
234 }
235
112 -function getTargetInstForChangeEvent(topLevelType, targetInst, targetNode) {
113 - if (topLevelType === 'topChange') {
114 - return getInstIfValueChanged(targetInst, targetNode);
236 +function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
237 + if (topLevelType === 'topInput' || topLevelType === 'topChange') {
238 + return getInstIfValueChanged(targetInst);
239 }
240 }
241
@@ -148,33 +272,34 @@ function handleControlledInputBlur(inst, node) {
272 var ChangeEventPlugin = {
273 eventTypes: eventTypes,
274
151 - extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
275 + _isInputEventSupported: isInputEventSupported,
276 +
277 + extractEvents: function(
278 + topLevelType,
279 + targetInst,
280 + nativeEvent,
281 + nativeEventTarget,
282 + ) {
283 var targetNode = targetInst
284 ? ReactDOMComponentTree.getNodeFromInstance(targetInst)
285 : window;
286
156 - // On the selectionchange event, the target is the document which isn't
157 - // helpful becasue we need the input, so we use the activeElement instead.
158 - if (!isTextInputEventSupported && topLevelType === 'topSelectionChange') {
159 - nativeEventTarget = targetNode = getActiveElement();
160 -
161 - if (targetNode) {
162 - targetInst = ReactDOMComponentTree.getInstanceFromNode(targetNode);
163 - }
164 - }
165 -
287 var getTargetInstFunc, handleEventFunc;
167 -
288 if (shouldUseChangeEvent(targetNode)) {
289 getTargetInstFunc = getTargetInstForChangeEvent;
170 - } else if (isTextInputElement(targetNode) && !isTextInputEventSupported) {
171 - getTargetInstFunc = getTargetInstForInputEventPolyfill;
172 - } else {
173 - getTargetInstFunc = getTargetInstForInputOrChangeEvent;
290 + } else if (isTextInputElement(targetNode)) {
291 + if (isInputEventSupported) {
292 + getTargetInstFunc = getTargetInstForInputOrChangeEvent;
293 + } else {
294 + getTargetInstFunc = getTargetInstForInputEventPolyfill;
295 + handleEventFunc = handleEventsForInputEventPolyfill;
296 + }
297 + } else if (shouldUseClickEvent(targetNode)) {
298 + getTargetInstFunc = getTargetInstForClickEvent;
299 }
300
301 if (getTargetInstFunc) {
177 - var inst = getTargetInstFunc(topLevelType, targetInst, targetNode);
302 + var inst = getTargetInstFunc(topLevelType, targetInst);
303 if (inst) {
304 var event = createAndAccumulateChangeEvent(
305 inst,
src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js
+10 -5
@@ -15,6 +15,7 @@ var React = require('react');
15 var ReactDOM = require('react-dom');
16 var ReactTestUtils = require('react-dom/test-utils');
17 // TODO: can we express this test with only public API?
18 +var ChangeEventPlugin = require('ChangeEventPlugin');
19 var inputValueTracking = require('inputValueTracking');
20
21 function getTrackedValue(elem) {
@@ -53,7 +54,7 @@ describe('ChangeEventPlugin', () => {
54 );
55
56 setUntrackedValue(input, true);
56 - ReactTestUtils.SimulateNative.change(input);
57 + ReactTestUtils.SimulateNative.click(input);
58
59 expect(called).toBe(1);
60 });
@@ -102,12 +103,12 @@ describe('ChangeEventPlugin', () => {
103 );
104
105 input.checked = true;
105 - ReactTestUtils.SimulateNative.change(input);
106 + ReactTestUtils.SimulateNative.click(input);
107 expect(called).toBe(0);
108
109 input.checked = false;
110 setTrackedValue(input, undefined);
110 - ReactTestUtils.SimulateNative.change(input);
111 + ReactTestUtils.SimulateNative.click(input);
112
113 expect(called).toBe(1);
114 });
@@ -130,8 +131,8 @@ describe('ChangeEventPlugin', () => {
131 <input type="radio" onChange={cb} />,
132 );
133 setUntrackedValue(input, true);
133 - ReactTestUtils.SimulateNative.change(input);
134 - ReactTestUtils.SimulateNative.input(input);
134 + ReactTestUtils.SimulateNative.click(input);
135 + ReactTestUtils.SimulateNative.click(input);
136 expect(called).toBe(1);
137 });
138
@@ -181,6 +182,10 @@ describe('ChangeEventPlugin', () => {
182 expect(e.type).toBe('change');
183 }
184
185 + if (!ChangeEventPlugin._isInputEventSupported) {
186 + return;
187 + }
188 +
189 var input = ReactTestUtils.renderIntoDocument(
190 <input type="range" onChange={cb} />,
191 );