{}} />);
- 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');
diff --git a/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js b/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js
index 1b1d5b95c0..fb56d1408f 100644
--- a/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js
+++ b/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js
@@ -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;
diff --git a/src/renderers/dom/shared/eventPlugins/SelectEventPlugin.js b/src/renderers/dom/shared/eventPlugins/SelectEventPlugin.js
index c4a51ff712..1989139b1a 100644
--- a/src/renderers/dom/shared/eventPlugins/SelectEventPlugin.js
+++ b/src/renderers/dom/shared/eventPlugins/SelectEventPlugin.js
@@ -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,
- };
}
}
diff --git a/src/renderers/dom/shared/eventPlugins/TapEventPlugin.js b/src/renderers/dom/shared/eventPlugins/TapEventPlugin.js
index bd780180cb..8edd53ae60 100644
--- a/src/renderers/dom/shared/eventPlugins/TapEventPlugin.js
+++ b/src/renderers/dom/shared/eventPlugins/TapEventPlugin.js
@@ -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 {
diff --git a/src/renderers/dom/shared/setInnerHTML.js b/src/renderers/dom/shared/setInnerHTML.js
index 70304e1951..25c7570071 100644
--- a/src/renderers/dom/shared/setInnerHTML.js
+++ b/src/renderers/dom/shared/setInnerHTML.js
@@ -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;
diff --git a/src/renderers/dom/shared/syntheticEvents/SyntheticMouseEvent.js b/src/renderers/dom/shared/syntheticEvents/SyntheticMouseEvent.js
index 6ff156c564..d7d2cbaa19 100644
--- a/src/renderers/dom/shared/syntheticEvents/SyntheticMouseEvent.js
+++ b/src/renderers/dom/shared/syntheticEvents/SyntheticMouseEvent.js
@@ -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;
- },
};
/**
diff --git a/src/renderers/dom/shared/utils/ViewportMetrics.js b/src/renderers/dom/shared/utils/ViewportMetrics.js
deleted file mode 100644
index c23d0275c3..0000000000
--- a/src/renderers/dom/shared/utils/ViewportMetrics.js
+++ /dev/null
@@ -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;
diff --git a/src/renderers/dom/stack/client/ReactDOMComponent.js b/src/renderers/dom/stack/client/ReactDOMComponent.js
index 8e73894697..ac717f96c7 100644
--- a/src/renderers/dom/stack/client/ReactDOMComponent.js
+++ b/src/renderers/dom/stack/client/ReactDOMComponent.js
@@ -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;
diff --git a/src/renderers/dom/stack/client/ReactMount.js b/src/renderers/dom/stack/client/ReactMount.js
index 20940c6fce..93c21c84a2 100644
--- a/src/renderers/dom/stack/client/ReactMount.js
+++ b/src/renderers/dom/stack/client/ReactMount.js
@@ -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) {
diff --git a/src/renderers/native/ReactNativeFiber.js b/src/renderers/native/ReactNativeFiber.js
index 014b4dfa85..4b0e71d613 100644
--- a/src/renderers/native/ReactNativeFiber.js
+++ b/src/renderers/native/ReactNativeFiber.js
@@ -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) {
diff --git a/src/renderers/native/ReactNativeFiberErrorDialog.js b/src/renderers/native/ReactNativeFiberErrorDialog.js
new file mode 100644
index 0000000000..3d65ac4663
--- /dev/null
+++ b/src/renderers/native/ReactNativeFiberErrorDialog.js
@@ -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
;
+
+ // 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;
diff --git a/src/renderers/native/ReactNativeStack.js b/src/renderers/native/ReactNativeStack.js
index 2a5b8f509b..0d1f323f02 100644
--- a/src/renderers/native/ReactNativeStack.js
+++ b/src/renderers/native/ReactNativeStack.js
@@ -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 */
diff --git a/src/renderers/native/takeSnapshot.js b/src/renderers/native/takeSnapshot.js
new file mode 100644
index 0000000000..98608c83e5
--- /dev/null
+++ b/src/renderers/native/takeSnapshot.js
@@ -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 | number,
+ options?: {
+ width?: number,
+ height?: number,
+ format?: 'png' | 'jpeg',
+ quality?: number,
+ },
+): Promise {
+ 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;
diff --git a/src/renderers/shared/fiber/ReactFiberErrorLogger.js b/src/renderers/shared/fiber/ReactFiberErrorLogger.js
index 4deb654932..7eb1f746b1 100644
--- a/src/renderers/shared/fiber/ReactFiberErrorLogger.js
+++ b/src/renderers/shared/fiber/ReactFiberErrorLogger.js
@@ -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(
diff --git a/src/renderers/shared/fiber/ReactFiberHostContext.js b/src/renderers/shared/fiber/ReactFiberHostContext.js
index b6252bbc0f..41e4ff15df 100644
--- a/src/renderers/shared/fiber/ReactFiberHostContext.js
+++ b/src/renderers/shared/fiber/ReactFiberHostContext.js
@@ -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 = {
getHostContext(): CX,
getRootHostContainer(): C,
@@ -44,19 +45,27 @@ module.exports = function(
getRootHostContext,
} = config;
- let contextStackCursor: StackCursor = createCursor((null: ?CX));
- let contextFiberStackCursor: StackCursor = createCursor(
- (null: Fiber | null),
+ let contextStackCursor: StackCursor = createCursor(
+ NO_CONTEXT,
);
- let rootInstanceStackCursor: StackCursor = createCursor((null: ?C));
+ let contextFiberStackCursor: StackCursor = createCursor(
+ NO_CONTEXT,
+ );
+ let rootInstanceStackCursor: StackCursor = createCursor(
+ NO_CONTEXT,
+ );
+
+ function requiredContext(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(
}
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(
}
function resetHostContainer() {
- contextStackCursor.current = null;
- rootInstanceStackCursor.current = null;
+ contextStackCursor.current = NO_CONTEXT;
+ rootInstanceStackCursor.current = NO_CONTEXT;
}
return {
diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js
index 29b74134ec..520b1c8954 100644
--- a/src/renderers/shared/fiber/ReactFiberScheduler.js
+++ b/src/renderers/shared/fiber/ReactFiberScheduler.js
@@ -684,6 +684,11 @@ module.exports = function(
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 {
diff --git a/src/renderers/shared/fiber/__tests__/ReactFiberHostContext-test.js b/src/renderers/shared/fiber/__tests__/ReactFiberHostContext-test.js
new file mode 100644
index 0000000000..b39352987c
--- /dev/null
+++ b/src/renderers/shared/fiber/__tests__/ReactFiberHostContext-test.js
@@ -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(
+ ,
+ container,
+ /* parentComponent: */ null,
+ /* callback: */ null,
+ );
+ expect(creates).toBe(2);
+ });
+});