Merge branch 'master' into rollup

This commit is contained in:
Dan Abramov
2017-03-31 13:34:14 +01:00
42 changed files with 431 additions and 435 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
**What is the current behavior?**
**If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar (template: https://jsfiddle.net/reactjs/69z2wepo/).**
**If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar (template: https://jsfiddle.net/84v837e9/).**
**What is the expected behavior?**
+2 -2
View File
@@ -21,8 +21,8 @@ The [`ReactTransitionGroup`](#low-level-api-reacttransitiongroup) add-on compone
**Importing**
```javascript
import ReactCSSTransitionGroup from 'react-addons-css-transition-group' // ES6
var ReactCSSTransitionGroup = require('react-addons-css-transition-group') // ES5 with npm
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; // ES6
var ReactCSSTransitionGroup = require('react-addons-css-transition-group'); // ES5 with npm
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; // ES5 with react-with-addons.js
```
+4 -4
View File
@@ -11,8 +11,8 @@ next: perf.html
**Importing**
```javascript
import createFragment from 'react-addons-create-fragment' // ES6
var createFragment = require('react-addons-create-fragment') // ES5 with npm
import createFragment from 'react-addons-create-fragment'; // ES6
var createFragment = require('react-addons-create-fragment'); // ES5 with npm
var createFragment = React.addons.createFragment; // ES5 with react-with-addons.js
```
@@ -42,8 +42,8 @@ To solve this problem, you can use the `createFragment` add-on to give keys to t
Instead of creating arrays, we write:
```js
import createFragment from 'react-addons-create-fragment'
```javascript
import createFragment from 'react-addons-create-fragment';
function Swapper(props) {
let children;
+2 -2
View File
@@ -11,8 +11,8 @@ next: test-utils.html
**Importing**
```javascript
import Perf from 'react-addons-perf' // ES6
var Perf = require('react-addons-perf') // ES5 with npm
import Perf from 'react-addons-perf'; // ES6
var Perf = require('react-addons-perf'); // ES5 with npm
var Perf = React.addons.Perf; // ES5 with react-with-addons.js
```
+2 -2
View File
@@ -12,8 +12,8 @@ category: Add-Ons
**Importing**
```javascript
import PureRenderMixin from 'react-addons-pure-render-mixin' // ES6
var PureRenderMixin = require('react-addons-pure-render-mixin') // ES5 with npm
import PureRenderMixin from 'react-addons-pure-render-mixin'; // ES6
var PureRenderMixin = require('react-addons-pure-render-mixin'); // ES5 with npm
var PureRenderMixin = React.addons.PureRenderMixin; // ES5 with react-with-addons.js
```
+2 -2
View File
@@ -12,8 +12,8 @@ category: Reference
**Importing**
```javascript
import shallowCompare from 'react-addons-shallow-compare' // ES6
var shallowCompare = require('react-addons-shallow-compare') // ES5 with npm
import shallowCompare from 'react-addons-shallow-compare'; // ES6
var shallowCompare = require('react-addons-shallow-compare'); // ES5 with npm
var shallowCompare = React.addons.shallowCompare; // ES5 with react-with-addons.js
```
+2 -2
View File
@@ -10,8 +10,8 @@ prev: perf.html
**Importing**
```javascript
import ReactTestUtils from 'react-addons-test-utils' // ES6
var ReactTestUtils = require('react-addons-test-utils') // ES5 with npm
import ReactTestUtils from 'react-addons-test-utils'; // ES6
var ReactTestUtils = require('react-addons-test-utils'); // ES5 with npm
var ReactTestUtils = React.addons.TestUtils; // ES5 with react-with-addons.js
```
+2 -2
View File
@@ -14,8 +14,8 @@ next: update.html
**Importing**
```javascript
import LinkedStateMixin from 'react-addons-linked-state-mixin' // ES6
var LinkedStateMixin = require('react-addons-linked-state-mixin') // ES5 with npm
import LinkedStateMixin from 'react-addons-linked-state-mixin'; // ES6
var LinkedStateMixin = require('react-addons-linked-state-mixin'); // ES5 with npm
var LinkedStateMixin = React.addons.LinkedStateMixin; // ES5 with react-with-addons.js
```
+2
View File
@@ -45,9 +45,11 @@ const Header = React.createClass({
<option value="/range-inputs">Range Inputs</option>
<option value="/text-inputs">Text Inputs</option>
<option value="/number-inputs">Number Input</option>
<option value="/password-inputs">Password Input</option>
<option value="/selects">Selects</option>
<option value="/textareas">Textareas</option>
<option value="/input-change-events">Input change events</option>
<option value="/buttons">Buttons</option>
</select>
</label>
<label htmlFor="react_version">
@@ -0,0 +1,41 @@
const React = window.React;
import FixtureSet from '../../FixtureSet';
import TestCase from '../../TestCase';
function onButtonClick() {
window.alert(`This shouldn't have happened!`);
}
export default class ButtonTestCases extends React.Component {
render() {
return (
<FixtureSet title="Buttons">
<TestCase
title="onClick with disabled buttons"
description="The onClick event handler should not be invoked when clicking on a disabled buyaton">
<TestCase.Steps>
<li>Click on the disabled button</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
Nothing should happen
</TestCase.ExpectedResult>
<button disabled onClick={onButtonClick}>Click Me</button>
</TestCase>
<TestCase
title="onClick with disabled buttons containing other elements"
description="The onClick event handler should not be invoked when clicking on a disabled button that contains other elements">
<TestCase.Steps>
<li>Click on the disabled button, which contains a span</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
Nothing should happen
</TestCase.ExpectedResult>
<button disabled onClick={onButtonClick}>
<span>Click Me</span>
</button>
</TestCase>
</FixtureSet>
);
}
}
@@ -4,7 +4,9 @@ import TextInputFixtures from './text-inputs';
import SelectFixtures from './selects';
import TextAreaFixtures from './textareas';
import InputChangeEvents from './input-change-events';
import NumberInputFixtures from './number-inputs/';
import NumberInputFixtures from './number-inputs';
import PasswordInputFixtures from './password-inputs';
import ButtonFixtures from './buttons';
/**
* A simple routing component that renders the appropriate
@@ -25,6 +27,10 @@ const FixturesPage = React.createClass({
return <InputChangeEvents />;
case '/number-inputs':
return <NumberInputFixtures />;
case '/password-inputs':
return <PasswordInputFixtures />;
case '/buttons':
return <ButtonFixtures />
default:
return <p>Please select a test fixture.</p>;
}
@@ -0,0 +1,34 @@
const React = window.React;
import Fixture from '../../Fixture';
const PasswordTestCase = React.createClass({
getInitialState() {
return { value: '' };
},
onChange(event) {
this.setState({ value: event.target.value })
},
render() {
return (
<Fixture>
<div>{this.props.children}</div>
<div className="control-box">
<fieldset>
<legend>Controlled</legend>
<input type="password" value={this.state.value} onChange={this.onChange} />
<span className="hint"> Value: {JSON.stringify(this.state.value)}</span>
</fieldset>
<fieldset>
<legend>Uncontrolled</legend>
<input type="password" defaultValue="" />
</fieldset>
</div>
</Fixture>
);
},
});
export default PasswordTestCase;
@@ -0,0 +1,33 @@
const React = window.React;
import FixtureSet from '../../FixtureSet';
import TestCase from '../../TestCase';
import PasswordTestCase from './PasswordTestCase'
const NumberInputs = React.createClass({
render() {
return (
<FixtureSet title="Password inputs" description="">
<TestCase
title="The show password icon"
description={`
Some browsers have an unmask password icon that React accidentally
prevents the display of.
`}
affectedBrowsers="IE Edge, IE 11">
<TestCase.Steps>
<li>Type any string (not an actual password</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
The field should include the "unmasking password" icon.
</TestCase.ExpectedResult>
<PasswordTestCase />
</TestCase>
</FixtureSet>
);
},
});
export default NumberInputs;
+6
View File
@@ -18,6 +18,12 @@ select {
width: 12rem;
}
button {
margin: 10px;
font-size: 18px;
padding: 5px;
}
.header {
background: #222;
+15
View File
@@ -26,6 +26,12 @@ declare module 'TextInputState' {
declare function blurTextInput(object : any) : void;
declare function focusTextInput(object : any) : void;
}
declare module 'ExceptionsManager' {
declare function handleException(
error: Error,
isFatal: boolean,
) : void;
}
declare module 'UIManager' {
declare var customBubblingEventTypes : Object;
declare var customDirectEventTypes : Object;
@@ -58,6 +64,15 @@ declare module 'UIManager' {
viewName : string,
props : ?Object,
) : void;
declare function __takeSnapshot(
view ?: 'window' | Element<any> | number,
options ?: {
width ?: number,
height ?: number,
format ?: 'png' | 'jpeg',
quality ?: number,
},
) : Promise<any>;
}
declare module 'View' {
declare var exports : typeof ReactComponent;
@@ -1,5 +1,4 @@
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
* should not warn when server-side rendering `onScroll`
* should warn about incorrect casing on properties (ssr)
* should warn about incorrect casing on event handlers (ssr)
* should warn about class (ssr)
+3 -2
View File
@@ -868,7 +868,6 @@ src/renderers/dom/shared/__tests__/ReactBrowserEventEmitter-test.js
* should work with event plugins without dependencies
* should work with event plugins with dependencies
* should bubble onTouchTap
* should not crash ensureScrollValueMonitoring when createEvent returns null
src/renderers/dom/shared/__tests__/ReactDOM-test.js
* allows a DOM element to be used with a string
@@ -952,7 +951,6 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
* should report component containing invalid styles
* should properly escape text content and attributes values
* unmounts children before unsetting DOM node info
* should warn about the `onScroll` issue when unsupported (IE8)
* should throw when an invalid tag name is used server-side
* should throw when an attack vector is used server-side
* should throw when an invalid tag name is used
@@ -1596,6 +1594,9 @@ src/renderers/shared/fiber/__tests__/ReactCoroutine-test.js
* should unmount a composite in a coroutine
* should handle deep updates in coroutine
src/renderers/shared/fiber/__tests__/ReactFiberHostContext-test.js
* works with null host context
src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
* should render a simple component
* should render a simple component, in steps if needed
+14
View File
@@ -0,0 +1,14 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
module.exports = {
handleException: jest.fn(),
};
+1
View File
@@ -20,6 +20,7 @@ var RCTUIManager = {
updateView: jest.fn(),
removeSubviewsFromContainerWithID: jest.fn(),
replaceExistingNonRootView: jest.fn(),
__takeSnapshot: jest.fn(),
customBubblingEventTypes: {
topBlur: {
phasedRegistrationNames: {
@@ -28,7 +28,6 @@ var {getCurrentFiberOwnerName} = require('ReactDebugCurrentFiber');
var emptyFunction = require('fbjs/lib/emptyFunction');
var invariant = require('fbjs/lib/invariant');
var isEventSupported = require('isEventSupported');
var setInnerHTML = require('setInnerHTML');
var setTextContent = require('setTextContent');
var inputValueTracking = require('inputValueTracking');
@@ -145,14 +144,6 @@ if (__DEV__) {
}
function ensureListeningTo(rootContainerElement, registrationName) {
if (__DEV__) {
// IE8 has no API for event capturing and the `onScroll` event doesn't
// bubble.
warning(
registrationName !== 'onScroll' || isEventSupported('scroll', true),
"This browser doesn't support the `onScroll` event",
);
}
var isDocumentFragment = rootContainerElement.nodeType === DOC_FRAGMENT_TYPE;
var doc = isDocumentFragment
? rootContainerElement
@@ -13,7 +13,6 @@
var EventPluginRegistry = require('EventPluginRegistry');
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
var ViewportMetrics = require('ViewportMetrics');
var getVendorPrefixedEventName = require('getVendorPrefixedEventName');
var isEventSupported = require('isEventSupported');
@@ -73,9 +72,7 @@ var isEventSupported = require('isEventSupported');
* React Core . General Purpose Event Plugin System
*/
var hasEventPageXY;
var alreadyListeningTo = {};
var isMonitoringScrollValue = false;
var reactTopListenersCounter = 0;
// For events like 'submit' which don't consistently bubble (which we trap at a
@@ -262,45 +259,22 @@ var ReactBrowserEventEmitter = Object.assign({}, ReactEventEmitterMixin, {
);
}
} else if (dependency === 'topScroll') {
if (isEventSupported('scroll', true)) {
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
'topScroll',
'scroll',
mountAt,
);
} else {
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
'topScroll',
'scroll',
ReactBrowserEventEmitter.ReactEventListener.WINDOW_HANDLE,
);
}
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
'topScroll',
'scroll',
mountAt,
);
} else if (dependency === 'topFocus' || dependency === 'topBlur') {
if (isEventSupported('focus', true)) {
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
'topFocus',
'focus',
mountAt,
);
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
'topBlur',
'blur',
mountAt,
);
} else if (isEventSupported('focusin')) {
// IE has `focusin` and `focusout` events which bubble.
// @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
'topFocus',
'focusin',
mountAt,
);
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
'topBlur',
'focusout',
mountAt,
);
}
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
'topFocus',
'focus',
mountAt,
);
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
'topBlur',
'blur',
mountAt,
);
// to make sure blur and focus event listeners are only attached once
isListening.topBlur = true;
@@ -367,41 +341,6 @@ var ReactBrowserEventEmitter = Object.assign({}, ReactEventEmitterMixin, {
handle,
);
},
/**
* Protect against document.createEvent() returning null
* Some popup blocker extensions appear to do this:
* https://github.com/facebook/react/issues/6887
*/
supportsEventPageXY: function() {
if (!document.createEvent) {
return false;
}
var ev = document.createEvent('MouseEvent');
return ev != null && 'pageX' in ev;
},
/**
* Listens to window scroll and resize events. We cache scroll values so that
* application code can access them without triggering reflows.
*
* ViewportMetrics is only used by SyntheticMouse/TouchEvent and only when
* pageX/pageY isn't supported (legacy browsers).
*
* NOTE: Scroll events do not bubble.
*
* @see http://www.quirksmode.org/dom/events/scroll.html
*/
ensureScrollValueMonitoring: function() {
if (hasEventPageXY === undefined) {
hasEventPageXY = ReactBrowserEventEmitter.supportsEventPageXY();
}
if (!hasEventPageXY && !isMonitoringScrollValue) {
var refresh = ViewportMetrics.refreshScrollValues;
ReactBrowserEventEmitter.ReactEventListener.monitorScrollValue(refresh);
isMonitoringScrollValue = true;
}
},
});
module.exports = ReactBrowserEventEmitter;
+2 -67
View File
@@ -11,8 +11,6 @@
'use strict';
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
var getNodeForCharacterOffset = require('getNodeForCharacterOffset');
var getTextContentAccessor = require('getTextContentAccessor');
@@ -25,39 +23,6 @@ function isCollapsed(anchorNode, anchorOffset, focusNode, focusOffset) {
return anchorNode === focusNode && anchorOffset === focusOffset;
}
/**
* Get the appropriate anchor and focus node/offset pairs for IE.
*
* The catch here is that IE's selection API doesn't provide information
* about whether the selection is forward or backward, so we have to
* behave as though it's always forward.
*
* IE text differs from modern selection in that it behaves as though
* block elements end with a new line. This means character offsets will
* differ between the two APIs.
*
* @param {DOMElement} node
* @return {object}
*/
function getIEOffsets(node) {
var selection = document.selection;
var selectedRange = selection.createRange();
var selectedLength = selectedRange.text.length;
// Duplicate selection so we can move range without breaking user selection.
var fromStart = selectedRange.duplicate();
fromStart.moveToElementText(node);
fromStart.setEndPoint('EndToStart', selectedRange);
var startOffset = fromStart.text.length;
var endOffset = startOffset + selectedLength;
return {
start: startOffset,
end: endOffset,
};
}
/**
* @param {DOMElement} node
* @return {?object}
@@ -130,32 +95,6 @@ function getModernOffsets(node) {
};
}
/**
* @param {DOMElement|DOMTextNode} node
* @param {object} offsets
*/
function setIEOffsets(node, offsets) {
var range = document.selection.createRange().duplicate();
var start, end;
if (offsets.end === undefined) {
start = offsets.start;
end = start;
} else if (offsets.start > offsets.end) {
start = offsets.end;
end = offsets.start;
} else {
start = offsets.start;
end = offsets.end;
}
range.moveToElementText(node);
range.moveStart('character', start);
range.setEndPoint('EndToStart', range);
range.moveEnd('character', end - start);
range.select();
}
/**
* In modern non-IE browsers, we can support both forward and backward
* selections.
@@ -204,21 +143,17 @@ function setModernOffsets(node, offsets) {
}
}
var useIEOffsets = ExecutionEnvironment.canUseDOM &&
'selection' in document &&
!('getSelection' in window);
var ReactDOMSelection = {
/**
* @param {DOMElement} node
*/
getOffsets: useIEOffsets ? getIEOffsets : getModernOffsets,
getOffsets: getModernOffsets,
/**
* @param {DOMElement|DOMTextNode} node
* @param {object} offsets
*/
setOffsets: useIEOffsets ? setIEOffsets : setModernOffsets,
setOffsets: setModernOffsets,
};
module.exports = ReactDOMSelection;
@@ -12,7 +12,6 @@
'use strict';
var EventListener = require('fbjs/lib/EventListener');
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
var PooledClass = require('PooledClass');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactGenericBatching = require('ReactGenericBatching');
@@ -111,8 +110,6 @@ var ReactEventListener = {
_enabled: true,
_handleTopLevel: null,
WINDOW_HANDLE: ExecutionEnvironment.canUseDOM ? window : null,
setHandleTopLevel: function(handleTopLevel) {
ReactEventListener._handleTopLevel = handleTopLevel;
},
@@ -98,20 +98,6 @@ var ReactInputSelection = {
start: input.selectionStart,
end: input.selectionEnd,
};
} else if (
document.selection &&
(input.nodeName && input.nodeName.toLowerCase() === 'input')
) {
// IE8 input.
var range = document.selection.createRange();
// There can only be one selection per document in IE, so it must
// be in our element.
if (range.parentElement() === input) {
selection = {
start: -range.moveStart('character', -input.value.length),
end: -range.moveEnd('character', -input.value.length),
};
}
} else {
// Content editable or old IE textarea.
selection = ReactDOMSelection.getOffsets(input);
@@ -136,15 +122,6 @@ var ReactInputSelection = {
if ('selectionStart' in input) {
input.selectionStart = start;
input.selectionEnd = Math.min(end, input.value.length);
} else if (
document.selection &&
(input.nodeName && input.nodeName.toLowerCase() === 'input')
) {
var range = input.createTextRange();
range.collapse(true);
range.moveStart('character', start);
range.moveEnd('character', end - start);
range.select();
} else {
ReactDOMSelection.setOffsets(input, offsets);
}
@@ -435,20 +435,4 @@ describe('ReactBrowserEventEmitter', () => {
expect(idCallOrder[1] === PARENT).toBe(true);
expect(idCallOrder[2] === GRANDPARENT).toBe(true);
});
it('should not crash ensureScrollValueMonitoring when createEvent returns null', () => {
var originalCreateEvent = document.createEvent;
document.createEvent = function() {
return null;
};
spyOn(document, 'createEvent');
try {
var hasEventPageXY = ReactBrowserEventEmitter.supportsEventPageXY();
expect(document.createEvent.calls.count()).toBe(1);
expect(hasEventPageXY).toBe(false);
} finally {
document.createEvent = originalCreateEvent;
}
});
});
@@ -1233,30 +1233,6 @@ describe('ReactDOMComponent', () => {
});
});
describe('onScroll warning', () => {
it('should warn about the `onScroll` issue when unsupported (IE8)', () => {
// Mock this here so we can mimic IE8 support. We require isEventSupported
// before React so it's pre-mocked before React would require it.
jest.resetModules().mock('isEventSupported');
var isEventSupported = require('isEventSupported');
isEventSupported.mockReturnValueOnce(false);
ReactTestUtils = require('ReactTestUtils');
spyOn(console, 'error');
ReactTestUtils.renderIntoDocument(<div onScroll={function() {}} />);
expectDev(console.error.calls.count()).toBe(1);
expectDev(console.error.calls.argsFor(0)[0]).toBe(
"Warning: This browser doesn't support the `onScroll` event",
);
});
it('should not warn when server-side rendering `onScroll`', () => {
spyOn(console, 'error');
ReactDOMServer.renderToString(<div onScroll={() => {}} />);
expectDev(console.error).not.toHaveBeenCalled();
});
});
describe('tag sanitization', () => {
it('should throw when an invalid tag name is used server-side', () => {
var hackzor = React.createElement('script tag');
@@ -71,13 +71,6 @@ function shouldUseChangeEvent(elem) {
(nodeName === 'input' && elem.type === 'file');
}
var doesChangeEventBubble = false;
if (ExecutionEnvironment.canUseDOM) {
// See `handleChange` comment below
doesChangeEventBubble = isEventSupported('change') &&
(!document.documentMode || document.documentMode > 8);
}
function manualDispatchChangeEvent(nativeEvent) {
var event = createAndAccumulateChangeEvent(
activeElementInst,
@@ -104,21 +97,6 @@ function runEventInBatch(event) {
EventPluginHub.processEventQueue(false);
}
function startWatchingForChangeEventIE8(target, targetInst) {
activeElement = target;
activeElementInst = targetInst;
activeElement.attachEvent('onchange', manualDispatchChangeEvent);
}
function stopWatchingForChangeEventIE8() {
if (!activeElement) {
return;
}
activeElement.detachEvent('onchange', manualDispatchChangeEvent);
activeElement = null;
activeElementInst = null;
}
function getInstIfValueChanged(targetInst) {
if (inputValueTracking.updateValueIfChanged(targetInst)) {
return targetInst;
@@ -131,17 +109,6 @@ function getTargetInstForChangeEvent(topLevelType, targetInst) {
}
}
function handleEventsForChangeEventIE8(topLevelType, target, targetInst) {
if (topLevelType === 'topFocus') {
// stopWatching() should be a noop here but we call it just in case we
// missed a blur event somehow.
stopWatchingForChangeEventIE8();
startWatchingForChangeEventIE8(target, targetInst);
} else if (topLevelType === 'topBlur') {
stopWatchingForChangeEventIE8();
}
}
/**
* SECTION: handle `input` event
*/
@@ -192,9 +159,6 @@ function handlePropertyChange(nativeEvent) {
function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
if (topLevelType === 'topFocus') {
// In IE8, we can capture almost all .value changes by adding a
// propertychange handler and looking for events with propertyName
// equal to 'value'
// In IE9, propertychange fires for most input events but is buggy and
// doesn't fire when text is deleted, but conveniently, selectionchange
// appears to fire in all of the remaining cases so we catch those and
@@ -305,11 +269,7 @@ var ChangeEventPlugin = {
var getTargetInstFunc, handleEventFunc;
if (shouldUseChangeEvent(targetNode)) {
if (doesChangeEventBubble) {
getTargetInstFunc = getTargetInstForChangeEvent;
} else {
handleEventFunc = handleEventsForChangeEventIE8;
}
getTargetInstFunc = getTargetInstForChangeEvent;
} else if (isTextInputElement(targetNode)) {
if (isInputEventSupported) {
getTargetInstFunc = getTargetInstForInputOrChangeEvent;
@@ -80,14 +80,6 @@ function getSelection(node) {
focusNode: selection.focusNode,
focusOffset: selection.focusOffset,
};
} else if (document.selection) {
var range = document.selection.createRange();
return {
parentElement: range.parentElement(),
text: range.text,
top: range.boundingTop,
left: range.boundingLeft,
};
}
}
@@ -16,7 +16,6 @@ var EventPluginUtils = require('EventPluginUtils');
var EventPropagators = require('EventPropagators');
var SyntheticUIEvent = require('SyntheticUIEvent');
var TouchEventUtils = require('fbjs/lib/TouchEventUtils');
var ViewportMetrics = require('ViewportMetrics');
var isStartish = EventPluginUtils.isStartish;
var isEndish = EventPluginUtils.isEndish;
@@ -70,9 +69,7 @@ function getAxisCoordOfEvent(
if (singleTouch) {
return singleTouch[axis.page];
}
return axis.page in nativeEvent
? nativeEvent[axis.page]
: nativeEvent[axis.client] + ViewportMetrics[axis.envScroll];
return nativeEvent[axis.page];
}
function getDistance(coords: CoordinatesType, nativeEvent: _Touch): number {
+1 -59
View File
@@ -11,20 +11,14 @@
'use strict';
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
var DOMNamespaces = require('DOMNamespaces');
var WHITESPACE_TEST = /^[ \r\n\t\f]/;
var NONVISIBLE_TEST = /<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/;
var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction');
// SVG temp container for IE lacking innerHTML
var reusableSVGContainer;
/**
* Set the innerHTML property of a node, ensuring that whitespace is preserved
* even in IE8.
* Set the innerHTML property of a node
*
* @param {DOMElement} node
* @param {string} html
@@ -47,56 +41,4 @@ var setInnerHTML = createMicrosoftUnsafeLocalFunction(function(node, html) {
}
});
if (ExecutionEnvironment.canUseDOM) {
// IE8: When updating a just created node with innerHTML only leading
// whitespace is removed. When updating an existing node with innerHTML
// whitespace in root TextNodes is also collapsed.
// @see quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html
// Feature detection; only IE8 is known to behave improperly like this.
var testElement = document.createElement('div');
testElement.innerHTML = ' ';
if (testElement.innerHTML === '') {
setInnerHTML = function(node, html) {
// Magic theory: IE8 supposedly differentiates between added and updated
// nodes when processing innerHTML, innerHTML on updated nodes suffers
// from worse whitespace behavior. Re-adding a node like this triggers
// the initial and more favorable whitespace behavior.
// TODO: What to do on a detached node?
if (node.parentNode) {
node.parentNode.replaceChild(node, node);
}
// We also implement a workaround for non-visible tags disappearing into
// thin air on IE8, this only happens if there is no visible text
// in-front of the non-visible tags. Piggyback on the whitespace fix
// and simply check if any non-visible tags appear in the source.
if (
WHITESPACE_TEST.test(html) ||
(html[0] === '<' && NONVISIBLE_TEST.test(html))
) {
// Recover leading whitespace by temporarily prepending any character.
// \uFEFF has the potential advantage of being zero-width/invisible.
// UglifyJS drops U+FEFF chars when parsing, so use String.fromCharCode
// in hopes that this is preserved even if "\uFEFF" is transformed to
// the actual Unicode character (by Babel, for example).
// https://github.com/mishoo/UglifyJS2/blob/v2.4.20/lib/parse.js#L216
node.innerHTML = String.fromCharCode(0xfeff) + html;
// deleteData leaves an empty `TextNode` which offsets the index of all
// children. Definitely want to avoid this.
var textNode = node.firstChild;
if (textNode.data.length === 1) {
node.removeChild(textNode);
} else {
textNode.deleteData(0, 1);
}
} else {
node.innerHTML = html;
}
};
}
testElement = null;
}
module.exports = setInnerHTML;
@@ -12,7 +12,6 @@
'use strict';
var SyntheticUIEvent = require('SyntheticUIEvent');
var ViewportMetrics = require('ViewportMetrics');
var getEventModifierState = require('getEventModifierState');
@@ -25,6 +24,8 @@ var MouseEventInterface = {
screenY: null,
clientX: null,
clientY: null,
pageX: null,
pageY: null,
ctrlKey: null,
shiftKey: null,
altKey: null,
@@ -51,17 +52,6 @@ var MouseEventInterface = {
? event.toElement
: event.fromElement);
},
// "Proprietary" Interface.
pageX: function(event) {
return 'pageX' in event
? event.pageX
: event.clientX + ViewportMetrics.currentScrollLeft;
},
pageY: function(event) {
return 'pageY' in event
? event.pageY
: event.clientY + ViewportMetrics.currentScrollTop;
},
};
/**
@@ -1,25 +0,0 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ViewportMetrics
*/
'use strict';
var ViewportMetrics = {
currentScrollLeft: 0,
currentScrollTop: 0,
refreshScrollValues: function(scrollPosition) {
ViewportMetrics.currentScrollLeft = scrollPosition.x;
ViewportMetrics.currentScrollTop = scrollPosition.y;
},
};
module.exports = ViewportMetrics;
@@ -34,7 +34,6 @@ var ReactServerRenderingTransaction = require('ReactServerRenderingTransaction')
var emptyFunction = require('fbjs/lib/emptyFunction');
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
var invariant = require('fbjs/lib/invariant');
var isEventSupported = require('isEventSupported');
var inputValueTracking = require('inputValueTracking');
var validateDOMNesting = require('validateDOMNesting');
var warning = require('fbjs/lib/warning');
@@ -138,14 +137,6 @@ function ensureListeningTo(inst, registrationName, transaction) {
if (transaction instanceof ReactServerRenderingTransaction) {
return;
}
if (__DEV__) {
// IE8 has no API for event capturing and the `onScroll` event doesn't
// bubble.
warning(
registrationName !== 'onScroll' || isEventSupported('scroll', true),
"This browser doesn't support the `onScroll` event",
);
}
var containerInfo = inst._hostContainerInfo;
var isDocumentFragment = containerInfo._node &&
containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;
@@ -14,7 +14,6 @@
var DOMLazyTree = require('DOMLazyTree');
var DOMProperty = require('DOMProperty');
var React = require('react');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var ReactCurrentOwner = require('react/lib/ReactCurrentOwner');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
@@ -382,7 +381,6 @@ var ReactMount = {
'_registerComponent(...): Target container is not a DOM element.',
);
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
var componentInstance = instantiateReactComponent(nextElement, false);
if (callback) {
+11
View File
@@ -12,10 +12,12 @@
'use strict';
const ReactFiberErrorLogger = require('ReactFiberErrorLogger');
const ReactFiberReconciler = require('ReactFiberReconciler');
const ReactGenericBatching = require('ReactGenericBatching');
const ReactNativeAttributePayload = require('ReactNativeAttributePayload');
const ReactNativeComponentTree = require('ReactNativeComponentTree');
const ReactNativeFiberErrorDialog = require('ReactNativeFiberErrorDialog');
const ReactNativeFiberHostComponent = require('ReactNativeFiberHostComponent');
const ReactNativeInjection = require('ReactNativeInjection');
const ReactNativeTagHandles = require('ReactNativeTagHandles');
@@ -27,6 +29,7 @@ const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationI
const emptyObject = require('fbjs/lib/emptyObject');
const findNodeHandle = require('findNodeHandle');
const invariant = require('fbjs/lib/invariant');
const takeSnapshot = require('takeSnapshot');
const {injectInternals} = require('ReactFiberDevToolsHook');
@@ -376,6 +379,12 @@ findNodeHandle.injection.injectFindNode((fiber: Fiber) =>
NativeRenderer.findHostInstance(fiber));
findNodeHandle.injection.injectFindRootNodeID(instance => instance);
// Intercept lifecycle errors and ensure they are shown with the correct stack
// trace within the native redbox component.
ReactFiberErrorLogger.injection.injectDialog(
ReactNativeFiberErrorDialog.showDialog,
);
const ReactNative = {
// External users of findNodeHandle() expect the host tag number return type.
// The injected findNodeHandle() strategy returns the instance wrapper though.
@@ -402,6 +411,8 @@ const ReactNative = {
return NativeRenderer.getPublicRootInstance(root);
},
takeSnapshot,
unmountComponentAtNode(containerTag: number) {
const root = roots.get(containerTag);
if (root) {
@@ -0,0 +1,57 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactNativeFiberErrorDialog
* @flow
*/
'use strict';
const ExceptionsManager = require('ExceptionsManager');
import type {CapturedError} from 'ReactFiberScheduler';
/**
* Intercept lifecycle errors and ensure they are shown with the correct stack
* trace within the native redbox component.
*/
function ReactNativeFiberErrorDialog(capturedError: CapturedError): boolean {
const {componentStack, error} = capturedError;
let errorMessage: string;
let errorStack: string;
let errorType: Class<Error>;
// Typically Errors are thrown but eg strings or null can be thrown as well.
if (error && typeof error === 'object') {
const {message, name} = error;
const summary = message ? `${name}: ${message}` : name;
errorMessage = `${summary}\n\nThis error is located at:${componentStack}`;
errorStack = error.stack;
errorType = error.constructor;
} else {
errorMessage = `Unspecified error at:${componentStack}`;
errorStack = '';
errorType = Error;
}
const newError = new errorType(errorMessage);
newError.stack = errorStack;
ExceptionsManager.handleException(newError, false);
// Return false here to prevent ReactFiberErrorLogger default behavior of
// logging error details to console.error. Calls to console.error are
// automatically routed to the native redbox controller, which we've already
// done above by calling ExceptionsManager.
return false;
}
module.exports.showDialog = ReactNativeFiberErrorDialog;
+4
View File
@@ -18,6 +18,7 @@ var ReactNativeStackInjection = require('ReactNativeStackInjection');
var ReactUpdates = require('ReactUpdates');
var findNodeHandle = require('findNodeHandle');
var takeSnapshot = require('takeSnapshot');
ReactNativeInjection.inject();
ReactNativeStackInjection.inject();
@@ -45,6 +46,9 @@ var ReactNative = {
},
render: render,
takeSnapshot,
unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode,
/* eslint-disable camelcase */
+54
View File
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule takeSnapshot
* @flow
*/
'use strict';
var ReactNative = require('ReactNative');
var UIManager = require('UIManager');
import type {Element} from 'React';
/**
* Capture an image of the screen, window or an individual view. The image
* will be stored in a temporary file that will only exist for as long as the
* app is running.
*
* The `view` argument can be the literal string `window` if you want to
* capture the entire window, or it can be a reference to a specific
* React Native component.
*
* The `options` argument may include:
* - width/height (number) - the width and height of the image to capture.
* - format (string) - either 'png' or 'jpeg'. Defaults to 'png'.
* - quality (number) - the quality when using jpeg. 0.0 - 1.0 (default).
*
* Returns a Promise.
* @platform ios
*/
function takeSnapshot(
view?: 'window' | Element<any> | number,
options?: {
width?: number,
height?: number,
format?: 'png' | 'jpeg',
quality?: number,
},
): Promise<any> {
if (typeof view !== 'number' && view !== 'window') {
view = ReactNative.findNodeHandle(view) || 'window';
}
// Call the hidden '__takeSnapshot' method; the main one throws an error to
// prevent accidental backwards-incompatible usage.
return UIManager.__takeSnapshot(view, options);
}
module.exports = takeSnapshot;
@@ -12,14 +12,23 @@
'use strict';
const emptyFunction = require('fbjs/lib/emptyFunction');
const invariant = require('fbjs/lib/invariant');
import type {CapturedError} from 'ReactFiberScheduler';
let showDialog = emptyFunction;
const defaultShowDialog = () => true;
let showDialog = defaultShowDialog;
function logCapturedError(capturedError: CapturedError): void {
const logError = showDialog(capturedError);
// Allow injected showDialog() to prevent default console.error logging.
// This enables renderers like ReactNative to better manage redbox behavior.
if (logError === false) {
return;
}
if (__DEV__) {
const {
componentName,
@@ -85,14 +94,16 @@ function logCapturedError(capturedError: CapturedError): void {
`React caught an error thrown by one of your components.\n\n${error.stack}`,
);
}
showDialog(capturedError);
}
exports.injection = {
injectDialog(fn: (e: CapturedError) => void) {
/**
* Display custom dialog for lifecycle errors.
* Return false to prevent default behavior of logging to console.error.
*/
injectDialog(fn: (e: CapturedError) => boolean) {
invariant(
showDialog === emptyFunction,
showDialog === defaultShowDialog,
'The custom dialog was already injected.',
);
invariant(
@@ -16,8 +16,6 @@ import type {Fiber} from 'ReactFiber';
import type {HostConfig} from 'ReactFiberReconciler';
import type {StackCursor} from 'ReactFiberStack';
const emptyObject = require('fbjs/lib/emptyObject');
const {
createCursor,
pop,
@@ -26,6 +24,9 @@ const {
const invariant = require('fbjs/lib/invariant');
declare class NoContextT {}
const NO_CONTEXT: NoContextT = ({}: any);
export type HostContext<C, CX> = {
getHostContext(): CX,
getRootHostContainer(): C,
@@ -44,19 +45,27 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
getRootHostContext,
} = config;
let contextStackCursor: StackCursor<CX | null> = createCursor((null: ?CX));
let contextFiberStackCursor: StackCursor<Fiber | null> = createCursor(
(null: Fiber | null),
let contextStackCursor: StackCursor<CX | NoContextT> = createCursor(
NO_CONTEXT,
);
let rootInstanceStackCursor: StackCursor<C | null> = createCursor((null: ?C));
let contextFiberStackCursor: StackCursor<Fiber | NoContextT> = createCursor(
NO_CONTEXT,
);
let rootInstanceStackCursor: StackCursor<C | NoContextT> = createCursor(
NO_CONTEXT,
);
function requiredContext<Value>(c: Value | NoContextT): Value {
invariant(
c !== NO_CONTEXT,
'Expected host context to exist. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
return (c: any);
}
function getRootHostContainer(): C {
const rootInstance = rootInstanceStackCursor.current;
invariant(
rootInstance !== null,
'Expected root container to exist. This error is likely caused by a ' +
'bug in React. Please file an issue.',
);
const rootInstance = requiredContext(rootInstanceStackCursor.current);
return rootInstance;
}
@@ -80,26 +89,13 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}
function getHostContext(): CX {
const context = contextStackCursor.current;
invariant(
context != null,
'Expected host context to exist. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
const context = requiredContext(contextStackCursor.current);
return context;
}
function pushHostContext(fiber: Fiber): void {
const rootInstance = rootInstanceStackCursor.current;
invariant(
rootInstance != null,
'Expected root host context to exist. This error is likely caused by ' +
'a bug in React. Please file an issue.',
);
const context = contextStackCursor.current !== null
? contextStackCursor.current
: emptyObject;
const rootInstance = requiredContext(rootInstanceStackCursor.current);
const context = requiredContext(contextStackCursor.current);
const nextContext = getChildHostContext(context, fiber.type, rootInstance);
// Don't push this Fiber's context unless it's unique.
@@ -125,8 +121,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}
function resetHostContainer() {
contextStackCursor.current = null;
rootInstanceStackCursor.current = null;
contextStackCursor.current = NO_CONTEXT;
rootInstanceStackCursor.current = NO_CONTEXT;
}
return {
@@ -684,6 +684,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
return null;
}
}
// Without this explicit null return Flow complains of invalid return type
// TODO Remove the above while(true) loop
// eslint-disable-next-line no-unreachable
return null;
}
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
@@ -0,0 +1,62 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/
'use strict';
var React;
var ReactFiberReconciler;
describe('ReactFiberHostContext', () => {
beforeEach(() => {
jest.resetModules();
React = require('React');
ReactFiberReconciler = require('ReactFiberReconciler');
});
it('works with null host context', () => {
var creates = 0;
var Renderer = ReactFiberReconciler({
prepareForCommit: function() {},
resetAfterCommit: function() {},
getRootHostContext: function() {
return null;
},
getChildHostContext: function() {
return null;
},
shouldSetTextContent: function() {
return false;
},
createInstance: function() {
creates++;
},
finalizeInitialChildren: function() {
return null;
},
appendInitialChild: function() {
return null;
},
appendChild: function() {
return null;
},
useSyncScheduling: true,
});
const container = Renderer.createContainer(/* root: */ null);
Renderer.updateContainer(
<a><b /></a>,
container,
/* parentComponent: */ null,
/* callback: */ null,
);
expect(creates).toBe(2);
});
});