(CHILD = c)} {...props} />;
- }
-
- class ChildWrapper extends React.PureComponent {
- render() {
- return
;
+// ReactBrowserEventEmitter was removed in Fire
+if (ReactFeatureFlags.enableReactDOMFire) {
+ it('Empty test', () => {});
+} else {
+ describe('ReactBrowserEventEmitter', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ LISTENER.mockClear();
+
+ // TODO: can we express this test with only public API?
+ EventPluginHub = require('events/EventPluginHub');
+ EventPluginRegistry = require('events/EventPluginRegistry');
+ React = require('react');
+ ReactDOM = require('react-dom');
+ ReactDOMComponentTree = require('../client/ReactDOMComponentTree');
+ ReactBrowserEventEmitter = require('../events/ReactBrowserEventEmitter');
+ ReactTestUtils = require('react-dom/test-utils');
+
+ container = document.createElement('div');
+ document.body.appendChild(container);
+
+ let GRANDPARENT_PROPS = {};
+ let PARENT_PROPS = {};
+ let CHILD_PROPS = {};
+
+ function Child(props) {
+ return
(CHILD = c)} {...props} />;
}
- }
-
- function renderTree() {
- ReactDOM.render(
-
(GRANDPARENT = c)} {...GRANDPARENT_PROPS}>
-
(PARENT = c)} {...PARENT_PROPS}>
-
-
-
,
- container,
- );
- }
-
- renderTree();
-
- getListener = function(node, eventName) {
- const inst = ReactDOMComponentTree.getInstanceFromNode(node);
- return EventPluginHub.getListener(inst, eventName);
- };
- putListener = function(node, eventName, listener) {
- switch (node) {
- case CHILD:
- CHILD_PROPS[eventName] = listener;
- break;
- case PARENT:
- PARENT_PROPS[eventName] = listener;
- break;
- case GRANDPARENT:
- GRANDPARENT_PROPS[eventName] = listener;
- break;
- }
- // Rerender with new event listeners
- renderTree();
- };
- deleteAllListeners = function(node) {
- switch (node) {
- case CHILD:
- CHILD_PROPS = {};
- break;
- case PARENT:
- PARENT_PROPS = {};
- break;
- case GRANDPARENT:
- GRANDPARENT_PROPS = {};
- break;
- }
- renderTree();
- };
- idCallOrder = [];
- });
+ class ChildWrapper extends React.PureComponent {
+ render() {
+ return
;
+ }
+ }
- afterEach(() => {
- document.body.removeChild(container);
- container = null;
- });
+ function renderTree() {
+ ReactDOM.render(
+
(GRANDPARENT = c)} {...GRANDPARENT_PROPS}>
+
(PARENT = c)} {...PARENT_PROPS}>
+
+
+
,
+ container,
+ );
+ }
- it('should store a listener correctly', () => {
- registerSimpleTestHandler();
- const listener = getListener(CHILD, ON_CLICK_KEY);
- expect(listener).toBe(LISTENER);
- });
+ renderTree();
- it('should retrieve a listener correctly', () => {
- registerSimpleTestHandler();
- const listener = getListener(CHILD, ON_CLICK_KEY);
- expect(listener).toEqual(LISTENER);
- });
+ getListener = function(node, eventName) {
+ const inst = ReactDOMComponentTree.getInstanceFromNode(node);
+ return EventPluginHub.getListener(inst, eventName);
+ };
+ putListener = function(node, eventName, listener) {
+ switch (node) {
+ case CHILD:
+ CHILD_PROPS[eventName] = listener;
+ break;
+ case PARENT:
+ PARENT_PROPS[eventName] = listener;
+ break;
+ case GRANDPARENT:
+ GRANDPARENT_PROPS[eventName] = listener;
+ break;
+ }
+ // Rerender with new event listeners
+ renderTree();
+ };
+ deleteAllListeners = function(node) {
+ switch (node) {
+ case CHILD:
+ CHILD_PROPS = {};
+ break;
+ case PARENT:
+ PARENT_PROPS = {};
+ break;
+ case GRANDPARENT:
+ GRANDPARENT_PROPS = {};
+ break;
+ }
+ renderTree();
+ };
+
+ idCallOrder = [];
+ });
- it('should clear all handlers when asked to', () => {
- registerSimpleTestHandler();
- deleteAllListeners(CHILD);
- const listener = getListener(CHILD, ON_CLICK_KEY);
- expect(listener).toBe(undefined);
- });
+ afterEach(() => {
+ document.body.removeChild(container);
+ container = null;
+ });
- it('should invoke a simple handler registered on a node', () => {
- registerSimpleTestHandler();
- CHILD.click();
- expect(LISTENER).toHaveBeenCalledTimes(1);
- });
+ it('should store a listener correctly', () => {
+ registerSimpleTestHandler();
+ const listener = getListener(CHILD, ON_CLICK_KEY);
+ expect(listener).toBe(LISTENER);
+ });
- it('should not invoke handlers if ReactBrowserEventEmitter is disabled', () => {
- registerSimpleTestHandler();
- ReactBrowserEventEmitter.setEnabled(false);
- CHILD.click();
- expect(LISTENER).toHaveBeenCalledTimes(0);
- ReactBrowserEventEmitter.setEnabled(true);
- CHILD.click();
- expect(LISTENER).toHaveBeenCalledTimes(1);
- });
+ it('should retrieve a listener correctly', () => {
+ registerSimpleTestHandler();
+ const listener = getListener(CHILD, ON_CLICK_KEY);
+ expect(listener).toEqual(LISTENER);
+ });
- it('should bubble simply', () => {
- putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
- putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
- putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
- CHILD.click();
- expect(idCallOrder.length).toBe(3);
- expect(idCallOrder[0]).toBe(CHILD);
- expect(idCallOrder[1]).toBe(PARENT);
- expect(idCallOrder[2]).toBe(GRANDPARENT);
- });
+ it('should clear all handlers when asked to', () => {
+ registerSimpleTestHandler();
+ deleteAllListeners(CHILD);
+ const listener = getListener(CHILD, ON_CLICK_KEY);
+ expect(listener).toBe(undefined);
+ });
- it('should bubble to the right handler after an update', () => {
- putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, 'GRANDPARENT'));
- putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, 'PARENT'));
- putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, 'CHILD'));
- CHILD.click();
- expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'GRANDPARENT']);
+ it('should invoke a simple handler registered on a node', () => {
+ registerSimpleTestHandler();
+ CHILD.click();
+ expect(LISTENER).toHaveBeenCalledTimes(1);
+ });
- idCallOrder = [];
+ it('should not invoke handlers if ReactBrowserEventEmitter is disabled', () => {
+ registerSimpleTestHandler();
+ ReactBrowserEventEmitter.setEnabled(false);
+ CHILD.click();
+ expect(LISTENER).toHaveBeenCalledTimes(0);
+ ReactBrowserEventEmitter.setEnabled(true);
+ CHILD.click();
+ expect(LISTENER).toHaveBeenCalledTimes(1);
+ });
- // Update just the grand parent without updating the child.
- putListener(
- GRANDPARENT,
- ON_CLICK_KEY,
- recordID.bind(null, 'UPDATED_GRANDPARENT'),
- );
+ it('should bubble simply', () => {
+ putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
+ putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
+ putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
+ CHILD.click();
+ expect(idCallOrder.length).toBe(3);
+ expect(idCallOrder[0]).toBe(CHILD);
+ expect(idCallOrder[1]).toBe(PARENT);
+ expect(idCallOrder[2]).toBe(GRANDPARENT);
+ });
- CHILD.click();
- expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'UPDATED_GRANDPARENT']);
- });
+ it('should bubble to the right handler after an update', () => {
+ putListener(
+ GRANDPARENT,
+ ON_CLICK_KEY,
+ recordID.bind(null, 'GRANDPARENT'),
+ );
+ putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, 'PARENT'));
+ putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, 'CHILD'));
+ CHILD.click();
+ expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'GRANDPARENT']);
+
+ idCallOrder = [];
+
+ // Update just the grand parent without updating the child.
+ putListener(
+ GRANDPARENT,
+ ON_CLICK_KEY,
+ recordID.bind(null, 'UPDATED_GRANDPARENT'),
+ );
- it('should continue bubbling if an error is thrown', () => {
- putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
- putListener(PARENT, ON_CLICK_KEY, function() {
- recordID(PARENT);
- throw new Error('Handler interrupted');
+ CHILD.click();
+ expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'UPDATED_GRANDPARENT']);
});
- putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
- expect(function() {
- ReactTestUtils.Simulate.click(CHILD);
- }).toThrow();
- expect(idCallOrder.length).toBe(3);
- expect(idCallOrder[0]).toBe(CHILD);
- expect(idCallOrder[1]).toBe(PARENT);
- expect(idCallOrder[2]).toBe(GRANDPARENT);
- });
- it('should set currentTarget', () => {
- putListener(CHILD, ON_CLICK_KEY, function(event) {
- recordID(CHILD);
- expect(event.currentTarget).toBe(CHILD);
- });
- putListener(PARENT, ON_CLICK_KEY, function(event) {
- recordID(PARENT);
- expect(event.currentTarget).toBe(PARENT);
+ it('should continue bubbling if an error is thrown', () => {
+ putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
+ putListener(PARENT, ON_CLICK_KEY, function() {
+ recordID(PARENT);
+ throw new Error('Handler interrupted');
+ });
+ putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
+ expect(function() {
+ ReactTestUtils.Simulate.click(CHILD);
+ }).toThrow();
+ expect(idCallOrder.length).toBe(3);
+ expect(idCallOrder[0]).toBe(CHILD);
+ expect(idCallOrder[1]).toBe(PARENT);
+ expect(idCallOrder[2]).toBe(GRANDPARENT);
});
- putListener(GRANDPARENT, ON_CLICK_KEY, function(event) {
- recordID(GRANDPARENT);
- expect(event.currentTarget).toBe(GRANDPARENT);
- });
- CHILD.click();
- expect(idCallOrder.length).toBe(3);
- expect(idCallOrder[0]).toBe(CHILD);
- expect(idCallOrder[1]).toBe(PARENT);
- expect(idCallOrder[2]).toBe(GRANDPARENT);
- });
- it('should support stopPropagation()', () => {
- putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
- putListener(
- PARENT,
- ON_CLICK_KEY,
- recordIDAndStopPropagation.bind(null, PARENT),
- );
- putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
- CHILD.click();
- expect(idCallOrder.length).toBe(2);
- expect(idCallOrder[0]).toBe(CHILD);
- expect(idCallOrder[1]).toBe(PARENT);
- });
+ it('should set currentTarget', () => {
+ putListener(CHILD, ON_CLICK_KEY, function(event) {
+ recordID(CHILD);
+ expect(event.currentTarget).toBe(CHILD);
+ });
+ putListener(PARENT, ON_CLICK_KEY, function(event) {
+ recordID(PARENT);
+ expect(event.currentTarget).toBe(PARENT);
+ });
+ putListener(GRANDPARENT, ON_CLICK_KEY, function(event) {
+ recordID(GRANDPARENT);
+ expect(event.currentTarget).toBe(GRANDPARENT);
+ });
+ CHILD.click();
+ expect(idCallOrder.length).toBe(3);
+ expect(idCallOrder[0]).toBe(CHILD);
+ expect(idCallOrder[1]).toBe(PARENT);
+ expect(idCallOrder[2]).toBe(GRANDPARENT);
+ });
- it('should support overriding .isPropagationStopped()', () => {
- // Ew. See D4504876.
- putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
- putListener(PARENT, ON_CLICK_KEY, function(e) {
- recordID(PARENT, e);
- // This stops React bubbling but avoids touching the native event
- e.isPropagationStopped = () => true;
+ it('should support stopPropagation()', () => {
+ putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
+ putListener(
+ PARENT,
+ ON_CLICK_KEY,
+ recordIDAndStopPropagation.bind(null, PARENT),
+ );
+ putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
+ CHILD.click();
+ expect(idCallOrder.length).toBe(2);
+ expect(idCallOrder[0]).toBe(CHILD);
+ expect(idCallOrder[1]).toBe(PARENT);
});
- putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
- CHILD.click();
- expect(idCallOrder.length).toBe(2);
- expect(idCallOrder[0]).toBe(CHILD);
- expect(idCallOrder[1]).toBe(PARENT);
- });
- it('should stop after first dispatch if stopPropagation', () => {
- putListener(
- CHILD,
- ON_CLICK_KEY,
- recordIDAndStopPropagation.bind(null, CHILD),
- );
- putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
- putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
- CHILD.click();
- expect(idCallOrder.length).toBe(1);
- expect(idCallOrder[0]).toBe(CHILD);
- });
+ it('should support overriding .isPropagationStopped()', () => {
+ // Ew. See D4504876.
+ putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
+ putListener(PARENT, ON_CLICK_KEY, function(e) {
+ recordID(PARENT, e);
+ // This stops React bubbling but avoids touching the native event
+ e.isPropagationStopped = () => true;
+ });
+ putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
+ CHILD.click();
+ expect(idCallOrder.length).toBe(2);
+ expect(idCallOrder[0]).toBe(CHILD);
+ expect(idCallOrder[1]).toBe(PARENT);
+ });
- it('should not stopPropagation if false is returned', () => {
- putListener(CHILD, ON_CLICK_KEY, recordIDAndReturnFalse.bind(null, CHILD));
- putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
- putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
- CHILD.click();
- expect(idCallOrder.length).toBe(3);
- expect(idCallOrder[0]).toBe(CHILD);
- expect(idCallOrder[1]).toBe(PARENT);
- expect(idCallOrder[2]).toBe(GRANDPARENT);
- });
+ it('should stop after first dispatch if stopPropagation', () => {
+ putListener(
+ CHILD,
+ ON_CLICK_KEY,
+ recordIDAndStopPropagation.bind(null, CHILD),
+ );
+ putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
+ putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
+ CHILD.click();
+ expect(idCallOrder.length).toBe(1);
+ expect(idCallOrder[0]).toBe(CHILD);
+ });
- /**
- * The entire event registration state of the world should be "locked-in" at
- * the time the event occurs. This is to resolve many edge cases that come
- * about from a listener on a lower-in-DOM node causing structural changes at
- * places higher in the DOM. If this lower-in-DOM node causes new content to
- * be rendered at a place higher-in-DOM, we need to be careful not to invoke
- * these new listeners.
- */
-
- it('should invoke handlers that were removed while bubbling', () => {
- const handleParentClick = jest.fn();
- const handleChildClick = function(event) {
- deleteAllListeners(PARENT);
- };
- putListener(CHILD, ON_CLICK_KEY, handleChildClick);
- putListener(PARENT, ON_CLICK_KEY, handleParentClick);
- CHILD.click();
- expect(handleParentClick).toHaveBeenCalledTimes(1);
- });
+ it('should not stopPropagation if false is returned', () => {
+ putListener(
+ CHILD,
+ ON_CLICK_KEY,
+ recordIDAndReturnFalse.bind(null, CHILD),
+ );
+ putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
+ putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
+ CHILD.click();
+ expect(idCallOrder.length).toBe(3);
+ expect(idCallOrder[0]).toBe(CHILD);
+ expect(idCallOrder[1]).toBe(PARENT);
+ expect(idCallOrder[2]).toBe(GRANDPARENT);
+ });
- it('should not invoke newly inserted handlers while bubbling', () => {
- const handleParentClick = jest.fn();
- const handleChildClick = function(event) {
+ /**
+ * The entire event registration state of the world should be "locked-in" at
+ * the time the event occurs. This is to resolve many edge cases that come
+ * about from a listener on a lower-in-DOM node causing structural changes at
+ * places higher in the DOM. If this lower-in-DOM node causes new content to
+ * be rendered at a place higher-in-DOM, we need to be careful not to invoke
+ * these new listeners.
+ */
+
+ it('should invoke handlers that were removed while bubbling', () => {
+ const handleParentClick = jest.fn();
+ const handleChildClick = function(event) {
+ deleteAllListeners(PARENT);
+ };
+ putListener(CHILD, ON_CLICK_KEY, handleChildClick);
putListener(PARENT, ON_CLICK_KEY, handleParentClick);
- };
- putListener(CHILD, ON_CLICK_KEY, handleChildClick);
- CHILD.click();
- expect(handleParentClick).toHaveBeenCalledTimes(0);
- });
+ CHILD.click();
+ expect(handleParentClick).toHaveBeenCalledTimes(1);
+ });
- it('should have mouse enter simulated by test utils', () => {
- putListener(CHILD, ON_MOUSE_ENTER_KEY, recordID.bind(null, CHILD));
- ReactTestUtils.Simulate.mouseEnter(CHILD);
- expect(idCallOrder.length).toBe(1);
- expect(idCallOrder[0]).toBe(CHILD);
- });
+ it('should not invoke newly inserted handlers while bubbling', () => {
+ const handleParentClick = jest.fn();
+ const handleChildClick = function(event) {
+ putListener(PARENT, ON_CLICK_KEY, handleParentClick);
+ };
+ putListener(CHILD, ON_CLICK_KEY, handleChildClick);
+ CHILD.click();
+ expect(handleParentClick).toHaveBeenCalledTimes(0);
+ });
- it('should listen to events only once', () => {
- spyOnDevAndProd(EventTarget.prototype, 'addEventListener');
- ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
- ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
- expect(EventTarget.prototype.addEventListener).toHaveBeenCalledTimes(1);
- });
+ it('should have mouse enter simulated by test utils', () => {
+ putListener(CHILD, ON_MOUSE_ENTER_KEY, recordID.bind(null, CHILD));
+ ReactTestUtils.Simulate.mouseEnter(CHILD);
+ expect(idCallOrder.length).toBe(1);
+ expect(idCallOrder[0]).toBe(CHILD);
+ });
- it('should work with event plugins without dependencies', () => {
- spyOnDevAndProd(EventTarget.prototype, 'addEventListener');
+ it('should listen to events only once', () => {
+ spyOnDevAndProd(EventTarget.prototype, 'addEventListener');
+ ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
+ ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
+ expect(EventTarget.prototype.addEventListener).toHaveBeenCalledTimes(1);
+ });
- ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
+ it('should work with event plugins without dependencies', () => {
+ spyOnDevAndProd(EventTarget.prototype, 'addEventListener');
- expect(EventTarget.prototype.addEventListener.calls.argsFor(0)[0]).toBe(
- 'click',
- );
- });
+ ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
- it('should work with event plugins with dependencies', () => {
- spyOnDevAndProd(EventTarget.prototype, 'addEventListener');
+ expect(EventTarget.prototype.addEventListener.calls.argsFor(0)[0]).toBe(
+ 'click',
+ );
+ });
- ReactBrowserEventEmitter.listenTo(ON_CHANGE_KEY, document);
+ it('should work with event plugins with dependencies', () => {
+ spyOnDevAndProd(EventTarget.prototype, 'addEventListener');
- const setEventListeners = [];
- const listenCalls = EventTarget.prototype.addEventListener.calls.allArgs();
- for (let i = 0; i < listenCalls.length; i++) {
- setEventListeners.push(listenCalls[i][1]);
- }
+ ReactBrowserEventEmitter.listenTo(ON_CHANGE_KEY, document);
+
+ const setEventListeners = [];
+ const listenCalls = EventTarget.prototype.addEventListener.calls.allArgs();
+ for (let i = 0; i < listenCalls.length; i++) {
+ setEventListeners.push(listenCalls[i][1]);
+ }
- const module = EventPluginRegistry.registrationNameModules[ON_CHANGE_KEY];
- const dependencies = module.eventTypes.change.dependencies;
- expect(setEventListeners.length).toEqual(dependencies.length);
+ const module = EventPluginRegistry.registrationNameModules[ON_CHANGE_KEY];
+ const dependencies = module.eventTypes.change.dependencies;
+ expect(setEventListeners.length).toEqual(dependencies.length);
- for (let i = 0; i < setEventListeners.length; i++) {
- expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy();
- }
+ for (let i = 0; i < setEventListeners.length; i++) {
+ expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy();
+ }
+ });
});
-});
+}
diff --git a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js
index e695a845ff5..0c81570454d 100644
--- a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js
@@ -14,6 +14,7 @@ describe('ReactDOMComponent', () => {
let ReactTestUtils;
let ReactDOM;
let ReactDOMServer;
+ let ReactFeatureFlags = require('shared/ReactFeatureFlags');
function normalizeCodeLocInfo(str) {
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
@@ -195,6 +196,7 @@ describe('ReactDOMComponent', () => {
);
expect(container.firstChild.hasAttribute('onunknown')).toBe(false);
expect(container.firstChild.onunknown).toBe(undefined);
+
expect(() =>
ReactDOM.render(
,
@@ -207,30 +209,32 @@ describe('ReactDOMComponent', () => {
expect(container.firstChild['on-unknown']).toBe(undefined);
});
- it('should warn for unknown function event handlers', () => {
- const container = document.createElement('div');
- expect(() =>
- ReactDOM.render(
, container),
- ).toWarnDev(
- 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
- );
- expect(container.firstChild.hasAttribute('onUnknown')).toBe(false);
- expect(container.firstChild.onUnknown).toBe(undefined);
- expect(() =>
- ReactDOM.render(
, container),
- ).toWarnDev(
- 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
- );
- expect(container.firstChild.hasAttribute('onunknown')).toBe(false);
- expect(container.firstChild.onunknown).toBe(undefined);
- expect(() =>
- ReactDOM.render(
, container),
- ).toWarnDev(
- 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
- );
- expect(container.firstChild.hasAttribute('on-unknown')).toBe(false);
- expect(container.firstChild['on-unknown']).toBe(undefined);
- });
+ if (!ReactFeatureFlags.enableReactDOMFire) {
+ it('should warn for unknown function event handlers', () => {
+ const container = document.createElement('div');
+ expect(() =>
+ ReactDOM.render(
, container),
+ ).toWarnDev(
+ 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
+ );
+ expect(container.firstChild.hasAttribute('onUnknown')).toBe(false);
+ expect(container.firstChild.onUnknown).toBe(undefined);
+ expect(() =>
+ ReactDOM.render(
, container),
+ ).toWarnDev(
+ 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
+ );
+ expect(container.firstChild.hasAttribute('onunknown')).toBe(false);
+ expect(container.firstChild.onunknown).toBe(undefined);
+ expect(() =>
+ ReactDOM.render(
, container),
+ ).toWarnDev(
+ 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
+ );
+ expect(container.firstChild.hasAttribute('on-unknown')).toBe(false);
+ expect(container.firstChild['on-unknown']).toBe(undefined);
+ });
+ }
it('should warn for badly cased React attributes', () => {
const container = document.createElement('div');
@@ -1900,12 +1904,22 @@ describe('ReactDOMComponent', () => {
ReactTestUtils.renderIntoDocument(
React.createElement('input', {type: 'text', oninput: '1'}),
);
- }).toWarnDev('onInput');
+ }).toWarnDev(
+ ReactFeatureFlags.enableReactDOMFire
+ ? 'Warning: Invalid event handler property `oninput`. React events use the camelCase naming convention, ' +
+ 'for example `onClick`.\n in input'
+ : 'onInput',
+ );
expect(() => {
ReactTestUtils.renderIntoDocument(
React.createElement('input', {type: 'text', onKeydown: '1'}),
);
- }).toWarnDev('onKeyDown');
+ }).toWarnDev(
+ ReactFeatureFlags.enableReactDOMFire
+ ? 'Warning: Expected `onKeydown` listener to be a function, instead got a value of `string` type.' +
+ '\n in input'
+ : 'onKeyDown',
+ );
});
it('should warn about class', () => {
diff --git a/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.js b/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.js
index ad4ecd7b81f..00ccd01b8a6 100644
--- a/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.js
+++ b/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.js
@@ -12,6 +12,7 @@
let React;
let ReactDOM;
+// Fire has a polyfill for this plugin
describe('BeforeInputEventPlugin', () => {
let container;
diff --git a/packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js b/packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js
index 33dd3e964ee..318e72755b5 100644
--- a/packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js
+++ b/packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js
@@ -45,7 +45,9 @@ describe('EnterLeaveEventPlugin', () => {
{
e.persist();
- leaveEvents.push(e);
+ // We do this for React Fire because the properties
+ // are dynamic getters and can change after creation
+ leaveEvents.push({target: e.target, relatedTarget: e.relatedTarget});
}}
/>,
iframeDocument.body.getElementsByTagName('div')[0],
diff --git a/packages/react-dom/src/events/__tests__/SelectEventPlugin-test.js b/packages/react-dom/src/events/__tests__/SelectEventPlugin-test.js
index 8ac5b3a1314..c3de6a2e203 100644
--- a/packages/react-dom/src/events/__tests__/SelectEventPlugin-test.js
+++ b/packages/react-dom/src/events/__tests__/SelectEventPlugin-test.js
@@ -12,6 +12,7 @@
let React;
let ReactDOM;
+// Fire has a polyfill for this plugin
describe('SelectEventPlugin', () => {
let container;
diff --git a/packages/react-dom/src/fire/ReactDOMFB.js b/packages/react-dom/src/fire/ReactDOMFB.js
new file mode 100644
index 00000000000..7e560d76011
--- /dev/null
+++ b/packages/react-dom/src/fire/ReactDOMFB.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import {findCurrentFiberUsingSlowPath} from 'react-reconciler/reflection';
+import {get as getInstance} from 'shared/ReactInstanceMap';
+import {addUserTimingListener} from 'shared/ReactFeatureFlags';
+
+import ReactDOM from './ReactFire';
+import {isEventsEnabled} from './events/ReactFireEvents';
+import {getClosestFiberFromDOMNode} from './ReactFireInternal';
+
+Object.assign(
+ (ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: any),
+ {
+ // These are real internal dependencies that are trickier to remove:
+ ReactBrowserEventEmitter: {
+ isEnabled: isEventsEnabled,
+ },
+ ReactFiberTreeReflection: {
+ findCurrentFiberUsingSlowPath,
+ },
+ ReactDOMComponentTree: {
+ getClosestInstanceFromNode: getClosestFiberFromDOMNode,
+ },
+ ReactInstanceMap: {
+ get: getInstance,
+ },
+ // Perf experiment
+ addUserTimingListener,
+ },
+);
+
+export default ReactDOM;
diff --git a/packages/react-dom/src/fire/ReactFire.js b/packages/react-dom/src/fire/ReactFire.js
index b1a7855ab24..1e47a73fdb2 100644
--- a/packages/react-dom/src/fire/ReactFire.js
+++ b/packages/react-dom/src/fire/ReactFire.js
@@ -7,87 +7,68 @@
* @flow
*/
-// This file is copy paste from ReactDOM with adjusted paths
-// and a different host config import (react-reconciler/inline.fire).
-// TODO: real implementation.
-// console.log('Hello from Fire entry point.');
-
-import type {ReactNodeList} from 'shared/ReactTypes';
-// TODO: This type is shared between the reconciler and ReactDOM, but will
-// eventually be lifted out to the renderer.
-import type {
- FiberRoot,
- Batch as FiberRootBatch,
-} from 'react-reconciler/src/ReactFiberRoot';
-import type {Container} from '../client/ReactDOMHostConfig';
-
-import '../shared/checkReact';
-import '../client/ReactDOMClientInjection';
-
+import {roots, ReactRoot, type Root} from './ReactFireRoots';
+import {
+ enqueueStateRestore,
+ restoreStateIfNeeded,
+ setRestoreImplementation,
+} from './controlled/ReactFireControlledState';
+import {setBatchingImplementation} from './ReactFireBatching';
import {
- computeUniqueAsyncExpiration,
- findHostInstanceWithNoPortals,
- updateContainerAtExpirationTime,
- flushRoot,
- createContainer,
- updateContainer,
batchedUpdates,
- unbatchedUpdates,
- interactiveUpdates,
- flushInteractiveUpdates,
- flushSync,
- flushControlled,
- injectIntoDevTools,
- getPublicRootInstance,
+ findHostInstanceWithNoPortals,
findHostInstance,
findHostInstanceWithWarning,
+ flushControlled,
+ flushInteractiveUpdates,
+ flushSync,
+ interactiveUpdates,
+ unbatchedUpdates,
} from 'react-reconciler/inline.fire';
-import {createPortal as createPortalImpl} from 'shared/ReactPortal';
-import {canUseDOM} from 'shared/ExecutionEnvironment';
-import {setBatchingImplementation} from 'events/ReactGenericBatching';
+import {restoreHostComponentInputControlledState} from './controlled/ReactFireInput';
+import {restoreHostComponentTextareaControlledState} from './controlled/ReactFireTextarea';
+import {restoreHostComponentSelectControlledState} from './controlled/ReactFireSelect';
+import {setupDevTools} from './ReactFireDevTools';
import {
- setRestoreImplementation,
- enqueueStateRestore,
- restoreStateIfNeeded,
-} from 'events/ReactControlledComponent';
-import {
- injection as EventPluginHubInjection,
- runEventsInBatch,
-} from 'events/EventPluginHub';
-import {eventNameDispatchConfigs} from 'events/EventPluginRegistry';
+ COMMENT_NODE,
+ DOCUMENT_NODE,
+ ELEMENT_NODE,
+ ROOT_ATTRIBUTE_NAME,
+} from './ReactFireDOMConfig';
+import {getPublicRootInstance, isValidContainer} from './ReactFireUtils';
import {
- accumulateTwoPhaseDispatches,
- accumulateDirectDispatches,
-} from 'events/EventPropagators';
-import {has as hasInstance} from 'shared/ReactInstanceMap';
-import ReactVersion from 'shared/ReactVersion';
-import ReactSharedInternals from 'shared/ReactSharedInternals';
-import getComponentName from 'shared/getComponentName';
+ getFiberFromDomNode,
+ getFiberPropsFromDomNodeInstance,
+} from './ReactFireInternal';
+import {proxyListener} from './events/ReactFireEvents';
+
+import type {ReactNodeList} from 'shared/ReactTypes';
import invariant from 'shared/invariant';
-import lowPriorityWarning from 'shared/lowPriorityWarning';
-import warningWithoutStack from 'shared/warningWithoutStack';
+import {createPortal as createPortalImpl} from 'shared/ReactPortal';
+import getComponentName from 'shared/getComponentName';
import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
-
-import {
- getInstanceFromNode,
- getNodeFromInstance,
- getFiberCurrentPropsFromNode,
- getClosestInstanceFromNode,
-} from '../client/ReactDOMComponentTree';
-import {restoreControlledState} from '../client/ReactDOMComponent';
-import {dispatchEvent} from '../events/ReactDOMEventListener';
-import {
- ELEMENT_NODE,
- COMMENT_NODE,
- DOCUMENT_NODE,
- DOCUMENT_FRAGMENT_NODE,
-} from '../shared/HTMLNodeType';
-import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
+import warningWithoutStack from 'shared/warningWithoutStack';
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+import {HostComponent, HostText} from 'shared/ReactWorkTags';
+import lowPriorityWarning from 'shared/lowPriorityWarning';
+import {has as hasInstance} from 'shared/ReactInstanceMap';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
+type RootOptions = {
+ hydrate?: boolean,
+};
+
+export type DOMContainer =
+ | (Element & {
+ _reactRootContainer: ?Root,
+ })
+ | (Document & {
+ _reactRootContainer: ?Root,
+ });
+
let topLevelUpdateWarnings;
-let warnOnInvalidCallback;
+let warnedAboutHydrateAPI = false;
let didWarnAboutUnstableCreatePortal = false;
if (__DEV__) {
@@ -110,10 +91,11 @@ if (__DEV__) {
}
topLevelUpdateWarnings = (container: DOMContainer) => {
- if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {
- const hostInstance = findHostInstanceWithNoPortals(
- container._reactRootContainer._internalRoot.current,
- );
+ if (roots.has(container) && container.nodeType !== COMMENT_NODE) {
+ const root = roots.get(container);
+ const hostInstance =
+ root !== undefined &&
+ findHostInstanceWithNoPortals(root._internalRoot.current);
if (hostInstance) {
warningWithoutStack(
hostInstance.parentNode === container,
@@ -125,9 +107,9 @@ if (__DEV__) {
}
}
- const isRootRenderedBySomeReact = !!container._reactRootContainer;
+ const isRootRenderedBySomeReact = roots.has(container);
const rootEl = getReactRootElementInContainer(container);
- const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
+ const hasNonRootReactChild = !!(rootEl && getFiberFromDomNode(rootEl));
warningWithoutStack(
!hasNonRootReactChild || isRootRenderedBySomeReact,
@@ -148,334 +130,103 @@ if (__DEV__) {
'for your app.',
);
};
-
- warnOnInvalidCallback = function(callback: mixed, callerName: string) {
- warningWithoutStack(
- callback === null || typeof callback === 'function',
- '%s(...): Expected the last optional `callback` argument to be a ' +
- 'function. Instead received: %s.',
- callerName,
- callback,
- );
- };
}
-setRestoreImplementation(restoreControlledState);
-
-export type DOMContainer =
- | (Element & {
- _reactRootContainer: ?Root,
- })
- | (Document & {
- _reactRootContainer: ?Root,
- });
-
-type Batch = FiberRootBatch & {
- render(children: ReactNodeList): Work,
- then(onComplete: () => mixed): void,
- commit(): void,
-
- // The ReactRoot constructor is hoisted but the prototype methods are not. If
- // we move ReactRoot to be above ReactBatch, the inverse error occurs.
- // $FlowFixMe Hoisting issue.
- _root: Root,
- _hasChildren: boolean,
- _children: ReactNodeList,
-
- _callbacks: Array<() => mixed> | null,
- _didComplete: boolean,
-};
+setRestoreImplementation((domNode: Element, tag: string, props: Object) => {
+ switch (tag) {
+ case 'input':
+ restoreHostComponentInputControlledState(domNode, props);
+ return;
+ case 'textarea':
+ restoreHostComponentTextareaControlledState(domNode, props);
+ return;
+ case 'select':
+ restoreHostComponentSelectControlledState(domNode, props);
+ return;
+ }
+});
-type Root = {
- render(children: ReactNodeList, callback: ?() => mixed): Work,
- unmount(callback: ?() => mixed): Work,
- legacy_renderSubtreeIntoContainer(
- parentComponent: ?React$Component
,
- children: ReactNodeList,
- callback: ?() => mixed,
- ): Work,
- createBatch(): Batch,
-
- _internalRoot: FiberRoot,
-};
+setBatchingImplementation(
+ batchedUpdates,
+ interactiveUpdates,
+ flushInteractiveUpdates,
+);
-function ReactBatch(root: ReactRoot) {
- const expirationTime = computeUniqueAsyncExpiration();
- this._expirationTime = expirationTime;
- this._root = root;
- this._next = null;
- this._callbacks = null;
- this._didComplete = false;
- this._hasChildren = false;
- this._children = null;
- this._defer = true;
-}
-ReactBatch.prototype.render = function(children: ReactNodeList) {
+function createPortal(
+ children: ReactNodeList,
+ container: DOMContainer,
+ key: ?string = null,
+) {
invariant(
- this._defer,
- 'batch.render: Cannot render a batch that already committed.',
- );
- this._hasChildren = true;
- this._children = children;
- const internalRoot = this._root._internalRoot;
- const expirationTime = this._expirationTime;
- const work = new ReactWork();
- updateContainerAtExpirationTime(
- children,
- internalRoot,
- null,
- expirationTime,
- work._onCommit,
+ isValidContainer(container),
+ 'Target container is not a DOM element.',
);
- return work;
-};
-ReactBatch.prototype.then = function(onComplete: () => mixed) {
- if (this._didComplete) {
- onComplete();
- return;
- }
- let callbacks = this._callbacks;
- if (callbacks === null) {
- callbacks = this._callbacks = [];
- }
- callbacks.push(onComplete);
-};
-ReactBatch.prototype.commit = function() {
- const internalRoot = this._root._internalRoot;
- let firstBatch = internalRoot.firstBatch;
+ // TODO: pass ReactDOM portal implementation as third argument
+ return createPortalImpl(children, container, null, key);
+}
+
+function createRoot(container: DOMContainer, options?: RootOptions): ReactRoot {
+ const functionName = enableStableConcurrentModeAPIs
+ ? 'createRoot'
+ : 'unstable_createRoot';
invariant(
- this._defer && firstBatch !== null,
- 'batch.commit: Cannot commit a batch multiple times.',
+ isValidContainer(container),
+ '%s(...): Target container is not a DOM element.',
+ functionName,
);
-
- if (!this._hasChildren) {
- // This batch is empty. Return.
- this._next = null;
- this._defer = false;
- return;
- }
-
- let expirationTime = this._expirationTime;
-
- // Ensure this is the first batch in the list.
- if (firstBatch !== this) {
- // This batch is not the earliest batch. We need to move it to the front.
- // Update its expiration time to be the expiration time of the earliest
- // batch, so that we can flush it without flushing the other batches.
- if (this._hasChildren) {
- expirationTime = this._expirationTime = firstBatch._expirationTime;
- // Rendering this batch again ensures its children will be the final state
- // when we flush (updates are processed in insertion order: last
- // update wins).
- // TODO: This forces a restart. Should we print a warning?
- this.render(this._children);
- }
-
- // Remove the batch from the list.
- let previous = null;
- let batch = firstBatch;
- while (batch !== this) {
- previous = batch;
- batch = batch._next;
- }
- invariant(
- previous !== null,
- 'batch.commit: Cannot commit a batch multiple times.',
- );
- previous._next = batch._next;
-
- // Add it to the front.
- this._next = firstBatch;
- firstBatch = internalRoot.firstBatch = this;
- }
-
- // Synchronously flush all the work up to this batch's expiration time.
- this._defer = false;
- flushRoot(internalRoot, expirationTime);
-
- // Pop the batch from the list.
- const next = this._next;
- this._next = null;
- firstBatch = internalRoot.firstBatch = next;
-
- // Append the next earliest batch's children to the update queue.
- if (firstBatch !== null && firstBatch._hasChildren) {
- firstBatch.render(firstBatch._children);
- }
-};
-ReactBatch.prototype._onComplete = function() {
- if (this._didComplete) {
- return;
- }
- this._didComplete = true;
- const callbacks = this._callbacks;
- if (callbacks === null) {
- return;
- }
- // TODO: Error handling.
- for (let i = 0; i < callbacks.length; i++) {
- const callback = callbacks[i];
- callback();
- }
-};
-
-type Work = {
- then(onCommit: () => mixed): void,
- _onCommit: () => void,
- _callbacks: Array<() => mixed> | null,
- _didCommit: boolean,
-};
-
-function ReactWork() {
- this._callbacks = null;
- this._didCommit = false;
- // TODO: Avoid need to bind by replacing callbacks in the update queue with
- // list of Work objects.
- this._onCommit = this._onCommit.bind(this);
+ const shouldHydrate = options != null && options.hydrate === true;
+ return new ReactRoot(container, true, shouldHydrate);
}
-ReactWork.prototype.then = function(onCommit: () => mixed): void {
- if (this._didCommit) {
- onCommit();
- return;
- }
- let callbacks = this._callbacks;
- if (callbacks === null) {
- callbacks = this._callbacks = [];
- }
- callbacks.push(onCommit);
-};
-ReactWork.prototype._onCommit = function(): void {
- if (this._didCommit) {
- return;
- }
- this._didCommit = true;
- const callbacks = this._callbacks;
- if (callbacks === null) {
- return;
- }
- // TODO: Error handling.
- for (let i = 0; i < callbacks.length; i++) {
- const callback = callbacks[i];
- invariant(
- typeof callback === 'function',
- 'Invalid argument passed as callback. Expected a function. Instead ' +
- 'received: %s',
- callback,
- );
- callback();
- }
-};
-function ReactRoot(
- container: Container,
- isConcurrent: boolean,
- hydrate: boolean,
-) {
- const root = createContainer(container, isConcurrent, hydrate);
- this._internalRoot = root;
-}
-ReactRoot.prototype.render = function(
- children: ReactNodeList,
- callback: ?() => mixed,
-): Work {
- const root = this._internalRoot;
- const work = new ReactWork();
- callback = callback === undefined ? null : callback;
+function findDOMNode(componentOrElement: Element | ?React$Component) {
if (__DEV__) {
- warnOnInvalidCallback(callback, 'render');
- }
- if (callback !== null) {
- work.then(callback);
+ let owner = (ReactCurrentOwner.current: any);
+ if (owner !== null && owner.stateNode !== null) {
+ const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;
+ warningWithoutStack(
+ warnedAboutRefsInRender,
+ '%s is accessing findDOMNode inside its render(). ' +
+ 'render() should be a pure function of props and state. It should ' +
+ 'never access something that requires stale data from the previous ' +
+ 'render, such as refs. Move this logic to componentDidMount and ' +
+ 'componentDidUpdate instead.',
+ getComponentName(owner.type) || 'A component',
+ );
+ owner.stateNode._warnedAboutRefsInRender = true;
+ }
}
- updateContainer(children, root, null, work._onCommit);
- return work;
-};
-ReactRoot.prototype.unmount = function(callback: ?() => mixed): Work {
- const root = this._internalRoot;
- const work = new ReactWork();
- callback = callback === undefined ? null : callback;
- if (__DEV__) {
- warnOnInvalidCallback(callback, 'render');
+ if (componentOrElement == null) {
+ return null;
}
- if (callback !== null) {
- work.then(callback);
+ if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
+ return (componentOrElement: any);
}
- updateContainer(null, root, null, work._onCommit);
- return work;
-};
-ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
- parentComponent: ?React$Component,
- children: ReactNodeList,
- callback: ?() => mixed,
-): Work {
- const root = this._internalRoot;
- const work = new ReactWork();
- callback = callback === undefined ? null : callback;
if (__DEV__) {
- warnOnInvalidCallback(callback, 'render');
- }
- if (callback !== null) {
- work.then(callback);
- }
- updateContainer(children, root, parentComponent, work._onCommit);
- return work;
-};
-ReactRoot.prototype.createBatch = function(): Batch {
- const batch = new ReactBatch(this);
- const expirationTime = batch._expirationTime;
-
- const internalRoot = this._internalRoot;
- const firstBatch = internalRoot.firstBatch;
- if (firstBatch === null) {
- internalRoot.firstBatch = batch;
- batch._next = null;
- } else {
- // Insert sorted by expiration time then insertion order
- let insertAfter = null;
- let insertBefore = firstBatch;
- while (
- insertBefore !== null &&
- insertBefore._expirationTime >= expirationTime
- ) {
- insertAfter = insertBefore;
- insertBefore = insertBefore._next;
- }
- batch._next = insertBefore;
- if (insertAfter !== null) {
- insertAfter._next = batch;
- }
+ return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');
}
-
- return batch;
-};
-
-/**
- * True if the supplied DOM node is a valid node element.
- *
- * @param {?DOMElement} node The candidate DOM node.
- * @return {boolean} True if the DOM is a valid DOM node.
- * @internal
- */
-function isValidContainer(node) {
- return !!(
- node &&
- (node.nodeType === ELEMENT_NODE ||
- node.nodeType === DOCUMENT_NODE ||
- node.nodeType === DOCUMENT_FRAGMENT_NODE ||
- (node.nodeType === COMMENT_NODE &&
- node.nodeValue === ' react-mount-point-unstable '))
- );
+ return findHostInstance(componentOrElement);
}
-function getReactRootElementInContainer(container: any) {
- if (!container) {
- return null;
- }
-
- if (container.nodeType === DOCUMENT_NODE) {
- return container.documentElement;
- } else {
- return container.firstChild;
+function cleanRootDOMContainer(container: DOMContainer) {
+ let warned = false;
+ let rootSibling;
+ while ((rootSibling = container.lastChild)) {
+ if (__DEV__) {
+ if (
+ !warned &&
+ rootSibling.nodeType === ELEMENT_NODE &&
+ (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
+ ) {
+ warned = true;
+ warningWithoutStack(
+ false,
+ 'render(): Target node has markup rendered by React, but there ' +
+ 'are unrelated nodes as well. This is most commonly caused by ' +
+ 'white-space inserted around server-rendered markup.',
+ );
+ }
+ }
+ container.removeChild(rootSibling);
}
}
@@ -488,271 +239,220 @@ function shouldHydrateDueToLegacyHeuristic(container) {
);
}
-setBatchingImplementation(
- batchedUpdates,
- interactiveUpdates,
- flushInteractiveUpdates,
-);
-
-let warnedAboutHydrateAPI = false;
-
-function legacyCreateRootFromDOMContainer(
- container: DOMContainer,
- forceHydrate: boolean,
-): Root {
- const shouldHydrate =
- forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
- // First clear any existing content.
- if (!shouldHydrate) {
- let warned = false;
- let rootSibling;
- while ((rootSibling = container.lastChild)) {
- if (__DEV__) {
- if (
- !warned &&
- rootSibling.nodeType === ELEMENT_NODE &&
- (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
- ) {
- warned = true;
- warningWithoutStack(
- false,
- 'render(): Target node has markup rendered by React, but there ' +
- 'are unrelated nodes as well. This is most commonly caused by ' +
- 'white-space inserted around server-rendered markup.',
- );
- }
- }
- container.removeChild(rootSibling);
- }
- }
- if (__DEV__) {
- if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
- warnedAboutHydrateAPI = true;
- lowPriorityWarning(
- false,
- 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
- 'will stop working in React v17. Replace the ReactDOM.render() call ' +
- 'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
- );
- }
- }
- // Legacy roots are not async by default.
- const isConcurrent = false;
- return new ReactRoot(container, isConcurrent, shouldHydrate);
-}
-
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component,
children: ReactNodeList,
- container: DOMContainer,
+ domContainer: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
- // TODO: Ensure all entry points contain this check
+ let root = roots.get(domContainer);
+
invariant(
- isValidContainer(container),
- 'Target container is not a DOM element.',
+ isValidContainer(domContainer),
+ 'unmountComponentAtNode(...): Target container is not a DOM element.',
);
if (__DEV__) {
- topLevelUpdateWarnings(container);
+ (domContainer: any)._reactRootDev = true;
+ topLevelUpdateWarnings(domContainer);
}
- // TODO: Without `any` type, Flow says "Property cannot be accessed on any
- // member of intersection type." Whyyyyyy.
- let root: Root = (container._reactRootContainer: any);
- if (!root) {
+ if (root === undefined) {
+ const shouldHydrate =
+ forceHydrate || shouldHydrateDueToLegacyHeuristic(domContainer);
// Initial mount
- root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
- container,
- forceHydrate,
- );
+ if (!shouldHydrate) {
+ cleanRootDOMContainer(domContainer);
+ }
+ if (__DEV__) {
+ if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
+ warnedAboutHydrateAPI = true;
+ lowPriorityWarning(
+ false,
+ 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
+ 'will stop working in React v17. Replace the ReactDOM.render() call ' +
+ 'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
+ );
+ }
+ }
+ root = new ReactRoot(domContainer, false, shouldHydrate);
+ roots.set(domContainer, ((root: any): Root));
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
- const instance = getPublicRootInstance(root._internalRoot);
+ const instance = getPublicRootInstance(
+ ((root: any): Root)._internalRoot,
+ );
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
unbatchedUpdates(() => {
if (parentComponent != null) {
- root.legacy_renderSubtreeIntoContainer(
+ ((root: any): Root).legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
- root.render(children, callback);
+ ((root: any): Root).render(children, callback);
}
});
} else {
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
- const instance = getPublicRootInstance(root._internalRoot);
+ const instance = getPublicRootInstance(
+ ((root: any): Root)._internalRoot,
+ );
originalCallback.call(instance);
};
}
// Update
if (parentComponent != null) {
- root.legacy_renderSubtreeIntoContainer(
+ ((root: any): Root).legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
- root.render(children, callback);
+ ((root: any): Root).render(children, callback);
}
}
- return getPublicRootInstance(root._internalRoot);
+ return getPublicRootInstance(((root: any): Root)._internalRoot);
}
-function createPortal(
+function render(
children: ReactNodeList,
- container: DOMContainer,
- key: ?string = null,
+ domContainer: DOMContainer,
+ callback: ?Function,
) {
- invariant(
- isValidContainer(container),
- 'Target container is not a DOM element.',
+ return legacyRenderSubtreeIntoContainer(
+ null,
+ children,
+ domContainer,
+ false,
+ callback,
);
- // TODO: pass ReactDOM portal implementation as third argument
- return createPortalImpl(children, container, null, key);
}
-const ReactDOM: Object = {
- createPortal,
-
- findDOMNode(
- componentOrElement: Element | ?React$Component,
- ): null | Element | Text {
- if (__DEV__) {
- let owner = (ReactCurrentOwner.current: any);
- if (owner !== null && owner.stateNode !== null) {
- const warnedAboutRefsInRender =
- owner.stateNode._warnedAboutRefsInRender;
- warningWithoutStack(
- warnedAboutRefsInRender,
- '%s is accessing findDOMNode inside its render(). ' +
- 'render() should be a pure function of props and state. It should ' +
- 'never access something that requires stale data from the previous ' +
- 'render, such as refs. Move this logic to componentDidMount and ' +
- 'componentDidUpdate instead.',
- getComponentName(owner.type) || 'A component',
- );
- owner.stateNode._warnedAboutRefsInRender = true;
- }
- }
- if (componentOrElement == null) {
- return null;
- }
- if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
- return (componentOrElement: any);
- }
- if (__DEV__) {
- return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');
- }
- return findHostInstance(componentOrElement);
- },
+function getReactRootElementInContainer(container: any) {
+ if (!container) {
+ return null;
+ }
- hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
- // TODO: throw or warn if we couldn't hydrate?
- return legacyRenderSubtreeIntoContainer(
- null,
- element,
- container,
- true,
- callback,
- );
- },
+ if (container.nodeType === DOCUMENT_NODE) {
+ return container.documentElement;
+ } else {
+ return container.firstChild;
+ }
+}
- render(
- element: React$Element,
- container: DOMContainer,
- callback: ?Function,
- ) {
- return legacyRenderSubtreeIntoContainer(
- null,
- element,
- container,
- false,
- callback,
- );
- },
+function unmountComponentAtNode(domContainer: DOMContainer) {
+ invariant(
+ isValidContainer(domContainer),
+ 'unmountComponentAtNode(...): Target container is not a DOM element.',
+ );
+ if (roots.has(domContainer)) {
+ unbatchedUpdates(() => {
+ legacyRenderSubtreeIntoContainer(null, null, domContainer, false, () => {
+ roots.delete(domContainer);
+ });
+ });
+ return true;
+ } else {
+ if (__DEV__ && (domContainer: any)._reactRootDev === true) {
+ warningWithoutStack(
+ false,
+ "unmountComponentAtNode(): The node you're attempting to unmount " +
+ 'was rendered by another copy of React.',
+ );
+ }
+ }
+ if (__DEV__) {
+ const rootEl = getReactRootElementInContainer(domContainer);
+ const hasNonRootReactChild = !!(rootEl && getFiberFromDomNode(rootEl));
- unstable_renderSubtreeIntoContainer(
- parentComponent: React$Component,
- element: React$Element,
- containerNode: DOMContainer,
- callback: ?Function,
- ) {
- invariant(
- parentComponent != null && hasInstance(parentComponent),
- 'parentComponent must be a valid React Component',
- );
- return legacyRenderSubtreeIntoContainer(
- parentComponent,
- element,
- containerNode,
- false,
- callback,
- );
- },
+ // Check if the container itself is a React root node.
+ const isContainerReactRoot =
+ domContainer.nodeType === ELEMENT_NODE &&
+ isValidContainer(domContainer.parentNode) &&
+ roots.has(domContainer.parentNode);
- unmountComponentAtNode(container: DOMContainer) {
- invariant(
- isValidContainer(container),
- 'unmountComponentAtNode(...): Target container is not a DOM element.',
+ warningWithoutStack(
+ !hasNonRootReactChild,
+ "unmountComponentAtNode(): The node you're attempting to unmount " +
+ 'was rendered by React and is not a top-level container. %s',
+ isContainerReactRoot
+ ? 'You may have accidentally passed in a React root node instead ' +
+ 'of its container.'
+ : 'Instead, have the parent component update its state and ' +
+ 'rerender in order to remove this component.',
);
+ }
+ return false;
+}
- if (container._reactRootContainer) {
- if (__DEV__) {
- const rootEl = getReactRootElementInContainer(container);
- const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
- warningWithoutStack(
- !renderedByDifferentReact,
- "unmountComponentAtNode(): The node you're attempting to unmount " +
- 'was rendered by another copy of React.',
- );
- }
+function hydrate(
+ element: React$Node,
+ container: DOMContainer,
+ callback: ?Function,
+) {
+ return legacyRenderSubtreeIntoContainer(
+ null,
+ element,
+ container,
+ true,
+ callback,
+ );
+}
- // Unmount should not be batched.
- unbatchedUpdates(() => {
- legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
- container._reactRootContainer = null;
- });
- });
- // If you call unmountComponentAtNode twice in quick succession, you'll
- // get `true` twice. That's probably fine?
- return true;
- } else {
- if (__DEV__) {
- const rootEl = getReactRootElementInContainer(container);
- const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
+/**
+ * Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding
+ * DOM node.
+ */
+function getDomNodeFromFiber(inst: Object) {
+ if (inst.tag === HostComponent || inst.tag === HostText) {
+ // In Fiber this, is just the state node right now. We assume it will be
+ // a host component or host text.
+ return inst.stateNode;
+ }
- // Check if the container itself is a React root node.
- const isContainerReactRoot =
- container.nodeType === ELEMENT_NODE &&
- isValidContainer(container.parentNode) &&
- !!container.parentNode._reactRootContainer;
+ // Without this first invariant, passing a non-DOM-component triggers the next
+ // invariant for a missing parent, which is super confusing.
+ invariant(false, 'getNodeFromInstance: Invalid argument.');
+}
- warningWithoutStack(
- !hasNonRootReactChild,
- "unmountComponentAtNode(): The node you're attempting to unmount " +
- 'was rendered by React and is not a top-level container. %s',
- isContainerReactRoot
- ? 'You may have accidentally passed in a React root node instead ' +
- 'of its container.'
- : 'Instead, have the parent component update its state and ' +
- 'rerender in order to remove this component.',
- );
- }
+// Ideally we should aim to remove this from React Fire
+function unstable_renderSubtreeIntoContainer(
+ parentComponent: React$Component,
+ element: React$Element,
+ containerNode: DOMContainer,
+ callback: ?Function,
+) {
+ invariant(
+ parentComponent != null && hasInstance(parentComponent),
+ 'parentComponent must be a valid React Component',
+ );
+ return legacyRenderSubtreeIntoContainer(
+ parentComponent,
+ element,
+ containerNode,
+ false,
+ callback,
+ );
+}
- return false;
- }
- },
+const noOp = () => {};
+const ReactDOM: Object = {
+ createPortal,
+ findDOMNode,
+ flushSync,
+ hydrate,
+ render,
+ unmountComponentAtNode,
// Temporary alias since we already shipped React 16 RC with it.
// TODO: remove in React 17.
unstable_createPortal(...args) {
@@ -768,88 +468,36 @@ const ReactDOM: Object = {
}
return createPortal(...args);
},
-
+ unstable_createRoot: undefined,
unstable_batchedUpdates: batchedUpdates,
-
- unstable_interactiveUpdates: interactiveUpdates,
-
- flushSync: flushSync,
-
- unstable_createRoot: createRoot,
unstable_flushControlled: flushControlled,
-
+ unstable_interactiveUpdates: interactiveUpdates,
+ unstable_renderSubtreeIntoContainer,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
// Keep in sync with ReactDOMUnstableNativeDependencies.js
// and ReactTestUtils.js. This is an array for better minification.
Events: [
- getInstanceFromNode,
- getNodeFromInstance,
- getFiberCurrentPropsFromNode,
- EventPluginHubInjection.injectEventPluginsByName,
- eventNameDispatchConfigs,
- accumulateTwoPhaseDispatches,
- accumulateDirectDispatches,
+ getFiberFromDomNode,
+ getDomNodeFromFiber,
+ getFiberPropsFromDomNodeInstance,
+ noOp,
+ null,
+ noOp,
+ noOp,
enqueueStateRestore,
restoreStateIfNeeded,
- dispatchEvent,
- runEventsInBatch,
+ proxyListener,
+ noOp,
],
},
};
-type RootOptions = {
- hydrate?: boolean,
-};
-
-function createRoot(container: DOMContainer, options?: RootOptions): ReactRoot {
- const functionName = enableStableConcurrentModeAPIs
- ? 'createRoot'
- : 'unstable_createRoot';
- invariant(
- isValidContainer(container),
- '%s(...): Target container is not a DOM element.',
- functionName,
- );
- const hydrate = options != null && options.hydrate === true;
- return new ReactRoot(container, true, hydrate);
-}
-
if (enableStableConcurrentModeAPIs) {
ReactDOM.createRoot = createRoot;
- ReactDOM.unstable_createRoot = undefined;
+} else {
+ ReactDOM.unstable_createRoot = createRoot;
}
-const foundDevTools = injectIntoDevTools({
- findFiberByHostInstance: getClosestInstanceFromNode,
- bundleType: __DEV__ ? 1 : 0,
- version: ReactVersion,
- rendererPackageName: 'react-dom',
-});
-
-if (__DEV__) {
- if (!foundDevTools && canUseDOM && window.top === window.self) {
- // If we're in Chrome or Firefox, provide a download link if not installed.
- if (
- (navigator.userAgent.indexOf('Chrome') > -1 &&
- navigator.userAgent.indexOf('Edge') === -1) ||
- navigator.userAgent.indexOf('Firefox') > -1
- ) {
- const protocol = window.location.protocol;
- // Don't warn in exotic cases like chrome-extension://.
- if (/^(https?|file):$/.test(protocol)) {
- console.info(
- '%cDownload the React DevTools ' +
- 'for a better development experience: ' +
- 'https://fb.me/react-devtools' +
- (protocol === 'file:'
- ? '\nYou might need to use a local HTTP server (instead of file://): ' +
- 'https://fb.me/react-devtools-faq'
- : ''),
- 'font-weight:bold',
- );
- }
- }
- }
-}
+setupDevTools();
export default ReactDOM;
diff --git a/packages/react-dom/src/fire/ReactFireBatching.js b/packages/react-dom/src/fire/ReactFireBatching.js
new file mode 100644
index 00000000000..8914ca4e0da
--- /dev/null
+++ b/packages/react-dom/src/fire/ReactFireBatching.js
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import {
+ needsStateRestore,
+ restoreStateIfNeeded,
+} from './controlled/ReactFireControlledState';
+
+// Used as a way to call batchedUpdates when we don't have a reference to
+// the renderer. Such as when we're dispatching events or if third party
+// libraries need to call batchedUpdates. Eventually, this API will go away when
+// everything is batched by default. We'll then have a similar API to opt-out of
+// scheduled work and instead do synchronous work.
+
+// Defaults
+let batchedUpdatesImpl = (fn, bookkeeping) => fn(bookkeeping);
+let interactiveUpdatesImpl = (fn, a, b, c) => fn(a, b, c);
+let flushInteractiveUpdatesImpl = () => {};
+
+let isBatching = false;
+export function batchedUpdates(fn, bookkeeping) {
+ if (isBatching) {
+ // If we are currently inside another batch, we need to wait until it
+ // fully completes before restoring state.
+ return fn(bookkeeping);
+ }
+ isBatching = true;
+ try {
+ return batchedUpdatesImpl(fn, bookkeeping);
+ } finally {
+ // Here we wait until all updates have propagated, which is important
+ // when using controlled components within layers:
+ // https://github.com/facebook/react/issues/1698
+ // Then we restore state of any controlled component.
+ isBatching = false;
+ const controlledComponentsHavePendingUpdates = needsStateRestore();
+ if (controlledComponentsHavePendingUpdates) {
+ // If a controlled event was fired, we may need to restore the state of
+ // the DOM node back to the controlled value. This is necessary when React
+ // bails out of the update without touching the DOM.
+ flushInteractiveUpdatesImpl();
+ restoreStateIfNeeded();
+ }
+ }
+}
+
+export function interactiveUpdates(fn, a, b, c) {
+ return interactiveUpdatesImpl(fn, a, b, c);
+}
+
+export function flushInteractiveUpdates() {
+ return flushInteractiveUpdatesImpl();
+}
+
+export function setBatchingImplementation(
+ _batchedUpdatesImpl,
+ _interactiveUpdatesImpl,
+ _flushInteractiveUpdatesImpl,
+) {
+ batchedUpdatesImpl = _batchedUpdatesImpl;
+ interactiveUpdatesImpl = _interactiveUpdatesImpl;
+ flushInteractiveUpdatesImpl = _flushInteractiveUpdatesImpl;
+}
diff --git a/packages/react-dom/src/fire/ReactFireComponent.js b/packages/react-dom/src/fire/ReactFireComponent.js
new file mode 100644
index 00000000000..38b273880a0
--- /dev/null
+++ b/packages/react-dom/src/fire/ReactFireComponent.js
@@ -0,0 +1,482 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import {createElement, isCustomComponent} from './ReactFireUtils';
+import {mediaEventTypesArr} from './events/ReactFireEventTypes';
+import {
+ applyHostComponentInputMountWrapper,
+ updateWrapper as applyHostComponentInputUpdateWrapper,
+ getHostComponentInputProps,
+ initHostComponentInputWrapperState,
+ updateChecked,
+} from './controlled/ReactFireInput';
+import {
+ applyHostComponentOptionMountWrapper,
+ getHostComponentOptionProps,
+ validateHostComponentOptionProps,
+} from './controlled/ReactFireOption';
+import {
+ applyHostComponentSelectMountWrapper,
+ applyHostComponentSelectUpdateWrapper,
+ getHostComponentSelectProps,
+ initHostComponentSelectWrapperState,
+} from './controlled/ReactFireSelect';
+import {
+ applyHostComponentTextareaMountWrapper,
+ updateWrapper as applyHostComponentTextareaUpdateWrapper,
+ getHostComponentTextareaProps,
+ initHostComponentTextareaWrapperState,
+} from './controlled/ReactFireTextarea';
+import {
+ ensureListeningTo,
+ trapBubbledEvent,
+ trapClickOnNonInteractiveElement,
+} from './events/ReactFireEvents';
+import {track} from './controlled/ReactFireValueTracking';
+import {
+ validateARIAProperties,
+ validateInputProperties,
+ validateUnknownProperties,
+} from './ReactFireValidation';
+import {
+ diffDOMElementProperties,
+ diffHydratedDOMElementProperties,
+ setDOMElementProperties,
+ updateDOMElementProperties,
+} from './ReactFireComponentProperties';
+import type {
+ HostContext,
+ HostContextDev,
+ HostContextProd,
+} from './ReactFireHostConfig';
+import {
+ ERROR,
+ INVALID,
+ LOAD,
+ RESET,
+ SUBMIT,
+ TOGGLE,
+} from './events/ReactFireEventTypes';
+
+import warning from 'shared/warning';
+import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCurrentFiber';
+
+let didWarnShadyDOM = false;
+let validatePropertiesInDevelopment;
+
+if (__DEV__) {
+ validatePropertiesInDevelopment = function(type, props) {
+ validateARIAProperties(type, props);
+ validateInputProperties(type, props);
+ validateUnknownProperties(type, props, /* canUseEventSystem */ true);
+ };
+}
+
+const specialHostComponentTypes = {
+ audio: typeIsVideoOrAudio,
+ details: typeIsDetails,
+ form: typeIsForm,
+ iframe: typeIsIframeOrObject,
+ image: typeIsImageOrLink,
+ img: typeIsImageOrLink,
+ input: typeIsInput,
+ link: typeIsImageOrLink,
+ object: typeIsIframeOrObject,
+ option: typeIsOption,
+ select: typeIsSelect,
+ source: typeIsSource,
+ textarea: typeIsTextarea,
+ video: typeIsVideoOrAudio,
+};
+
+function typeIsTextarea(
+ props: Object,
+ domNode: Element,
+ rootContainerElement: Element | Document,
+) {
+ initHostComponentTextareaWrapperState(domNode, props);
+ props = getHostComponentTextareaProps(domNode, props);
+ trapBubbledEvent('invalid', domNode);
+ // For controlled components we always need to ensure we're listening
+ // to onChange. Even if there is no listener.
+ ensureListeningTo(rootContainerElement, 'onChange');
+ return props;
+}
+
+function typeIsSelect(
+ props: Object,
+ domNode: Element,
+ rootContainerElement: Element | Document,
+) {
+ initHostComponentSelectWrapperState(domNode, props);
+ props = getHostComponentSelectProps(domNode, props);
+ trapBubbledEvent('invalid', domNode);
+ // For controlled components we always need to ensure we're listening
+ // to onChange. Even if there is no listener.
+ ensureListeningTo(rootContainerElement, 'onChange');
+ return props;
+}
+
+function typeIsOption(
+ props: Object,
+ domNode: Element,
+ rootContainerElement: Element | Document,
+) {
+ validateHostComponentOptionProps(domNode, props);
+ return getHostComponentOptionProps(domNode, props);
+}
+
+function typeIsInput(
+ props: Object,
+ domNode: Element,
+ rootContainerElement: Element | Document,
+) {
+ initHostComponentInputWrapperState(domNode, props);
+ props = getHostComponentInputProps(domNode, props);
+ trapBubbledEvent(INVALID, domNode);
+ // For controlled components we always need to ensure we're listening
+ // to onChange. Even if there is no listener.
+ ensureListeningTo(rootContainerElement, 'onChange');
+ return props;
+}
+
+function typeIsDetails(
+ props: Object,
+ domNode: Element,
+ rootContainerElement: Element | Document,
+) {
+ trapBubbledEvent(TOGGLE, domNode);
+ return props;
+}
+
+function typeIsForm(
+ props: Object,
+ domNode: Element,
+ rootContainerElement: Element | Document,
+) {
+ trapBubbledEvent(RESET, domNode);
+ trapBubbledEvent(SUBMIT, domNode);
+ return props;
+}
+
+function typeIsImageOrLink(
+ props: Object,
+ domNode: Element,
+ rootContainerElement: Element | Document,
+) {
+ trapBubbledEvent(ERROR, domNode);
+ trapBubbledEvent(LOAD, domNode);
+ return props;
+}
+
+function typeIsIframeOrObject(
+ props: Object,
+ domNode: Element,
+ rootContainerElement: Element | Document,
+) {
+ trapBubbledEvent(LOAD, domNode);
+ return props;
+}
+
+function typeIsVideoOrAudio(
+ props: Object,
+ domNode: Element,
+ rootContainerElement: Element | Document,
+) {
+ // Create listener for each media event
+ for (let i = 0; i < mediaEventTypesArr.length; i++) {
+ // TODO should this be bubbled still? I think it should be captured instead...
+ trapBubbledEvent(mediaEventTypesArr[i], domNode);
+ }
+ return props;
+}
+
+function typeIsSource(
+ props: Object,
+ domNode: Element,
+ rootContainerElement: Element | Document,
+) {
+ trapBubbledEvent(ERROR, domNode);
+ return props;
+}
+
+export function createHostComponent(
+ type: string,
+ props: Object,
+ rootContainerInstance: Element | Document,
+ hostContext: HostContext,
+) {
+ const parentNamespace = __DEV__
+ ? ((hostContext: any): HostContextDev).namespace
+ : ((hostContext: any): HostContextProd);
+ const domElement = createElement(
+ type,
+ props,
+ rootContainerInstance,
+ parentNamespace,
+ );
+ return domElement;
+}
+
+export function setHostComponentInitialProps(
+ type: string,
+ rawProps: Object,
+ domNode: Element,
+ rootContainerElement: Element | Document,
+ hostContext: HostContext,
+) {
+ const isCustomComponentTag = isCustomComponent(type, rawProps);
+ if (__DEV__) {
+ validatePropertiesInDevelopment(type, rawProps);
+ if (isCustomComponentTag && !didWarnShadyDOM && (domNode: any).shadyRoot) {
+ warning(
+ false,
+ '%s is using shady DOM. Using shady DOM with React can ' +
+ 'cause things to break subtly.',
+ getCurrentFiberOwnerNameInDevOrNull() || 'A component',
+ );
+ didWarnShadyDOM = true;
+ }
+ }
+ const specicalHostComponentTypeFunc = specialHostComponentTypes.hasOwnProperty(
+ type,
+ )
+ ? specialHostComponentTypes[type]
+ : null;
+ let props = rawProps;
+
+ if (specicalHostComponentTypeFunc !== null) {
+ props = specicalHostComponentTypeFunc(
+ rawProps,
+ domNode,
+ rootContainerElement,
+ );
+ }
+
+ setDOMElementProperties(
+ type,
+ props,
+ domNode,
+ rootContainerElement,
+ isCustomComponentTag,
+ );
+
+ if (specicalHostComponentTypeFunc !== null) {
+ switch (type) {
+ case 'input':
+ // TODO: Make sure we check if this is still unmounted or do any clean
+ // up necessary since we never stop tracking anymore.
+ track((domNode: any));
+ applyHostComponentInputMountWrapper(domNode, rawProps, false);
+ break;
+ case 'textarea':
+ // TODO: Make sure we check if this is still unmounted or do any clean
+ // up necessary since we never stop tracking anymore.
+ track((domNode: any));
+ applyHostComponentTextareaMountWrapper(domNode, rawProps);
+ break;
+ case 'option':
+ applyHostComponentOptionMountWrapper(domNode, rawProps);
+ break;
+ case 'select':
+ applyHostComponentSelectMountWrapper(domNode, rawProps);
+ break;
+ default:
+ }
+ } else {
+ if (typeof props.onClick === 'function') {
+ // TODO: This cast may not be sound for SVG, MathML or custom elements.
+ trapClickOnNonInteractiveElement(((domNode: any): HTMLElement));
+ }
+ }
+}
+
+// Calculate the diff between the two objects.
+export function diffHostComponentProperties(
+ domNode: Element,
+ type: string,
+ lastRawProps: Object,
+ nextRawProps: Object,
+ rootContainerElement: Element | Document,
+): null | Array {
+ if (__DEV__) {
+ validatePropertiesInDevelopment(type, nextRawProps);
+ }
+
+ let updatePayload: null | Array = null;
+ let lastProps: Object;
+ let nextProps: Object;
+ switch (type) {
+ case 'input':
+ lastProps = getHostComponentInputProps(domNode, lastRawProps);
+ nextProps = getHostComponentInputProps(domNode, nextRawProps);
+ updatePayload = [];
+ break;
+ case 'option':
+ lastProps = getHostComponentOptionProps(domNode, lastRawProps);
+ nextProps = getHostComponentOptionProps(domNode, nextRawProps);
+ updatePayload = [];
+ break;
+ case 'select':
+ lastProps = getHostComponentSelectProps(domNode, lastRawProps);
+ nextProps = getHostComponentSelectProps(domNode, nextRawProps);
+ updatePayload = [];
+ break;
+ case 'textarea':
+ lastProps = getHostComponentTextareaProps(domNode, lastRawProps);
+ nextProps = getHostComponentTextareaProps(domNode, nextRawProps);
+ updatePayload = [];
+ break;
+ default:
+ lastProps = lastRawProps;
+ nextProps = nextRawProps;
+ if (
+ typeof lastProps.onClick !== 'function' &&
+ typeof nextProps.onClick === 'function'
+ ) {
+ // TODO: This cast may not be sound for SVG, MathML or custom elements.
+ trapClickOnNonInteractiveElement(((domNode: any): HTMLElement));
+ }
+ break;
+ }
+
+ return diffDOMElementProperties(
+ domNode,
+ type,
+ updatePayload,
+ lastProps,
+ nextProps,
+ rootContainerElement,
+ );
+}
+
+export function updateHostComponentProperties(
+ domNode: Element,
+ updatePayload: Array,
+ type: string,
+ lastRawProps: Object,
+ nextRawProps: Object,
+) {
+ // Update checked *before* name.
+ // In the middle of an update, it is possible to have multiple checked.
+ // When a checked radio tries to change name, browser makes another radio's checked false.
+ if (
+ type === 'input' &&
+ nextRawProps.type === 'radio' &&
+ nextRawProps.name != null
+ ) {
+ updateChecked(domNode, nextRawProps);
+ }
+
+ const wasCustomComponentTag = isCustomComponent(type, lastRawProps);
+ const isCustomComponentTag = isCustomComponent(type, nextRawProps);
+
+ // Apply the diff.
+ updateDOMElementProperties(
+ domNode,
+ lastRawProps,
+ updatePayload,
+ wasCustomComponentTag,
+ isCustomComponentTag,
+ );
+
+ // TODO: Ensure that an update gets scheduled if any of the special props
+ // changed.
+ switch (type) {
+ case 'input':
+ // Update the wrapper around inputs *after* updating props. This has to
+ // happen after `updateDOMProperties`. Otherwise HTML5 input validations
+ // raise warnings and prevent the new value from being assigned.
+ applyHostComponentInputUpdateWrapper(domNode, nextRawProps);
+ break;
+ case 'textarea':
+ applyHostComponentTextareaUpdateWrapper(domNode, nextRawProps);
+ break;
+ case 'select':
+ //