UNPKG

react-native

Version:

A framework for building native apps using React

1,618 lines (1,478 loc) • 414 kB
/** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @noflow * @providesModule ReactNativeRenderer-dev * @preventMunge */ 'use strict'; if (__DEV__) { (function() { "use strict"; require("InitializeCore"); var invariant = require("fbjs/lib/invariant"); var warning = require("fbjs/lib/warning"); var emptyFunction = require("fbjs/lib/emptyFunction"); var RCTEventEmitter = require("RCTEventEmitter"); var UIManager = require("UIManager"); var React = require("react"); var ExceptionsManager = require("ExceptionsManager"); var TextInputState = require("TextInputState"); var deepDiffer = require("deepDiffer"); var flattenStyle = require("flattenStyle"); var emptyObject = require("fbjs/lib/emptyObject"); var checkPropTypes = require("prop-types/checkPropTypes"); var shallowEqual = require("fbjs/lib/shallowEqual"); var deepFreezeAndThrowOnMutationInDev = require("deepFreezeAndThrowOnMutationInDev"); var ReactErrorUtils = { // Used by Fiber to simulate a try-catch. _caughtError: null, _hasCaughtError: false, // Used by event system to capture/rethrow the first error. _rethrowError: null, _hasRethrowError: false, injection: { injectErrorUtils: function(injectedErrorUtils) { invariant( typeof injectedErrorUtils.invokeGuardedCallback === "function", "Injected invokeGuardedCallback() must be a function." ); invokeGuardedCallback = injectedErrorUtils.invokeGuardedCallback; } }, /** * Call a function while guarding against errors that happens within it. * Returns an error if it throws, otherwise null. * * In production, this is implemented using a try-catch. The reason we don't * use a try-catch directly is so that we can swap out a different * implementation in DEV mode. * * @param {String} name of the guard to use for logging or debugging * @param {Function} func The function to invoke * @param {*} context The context to use when calling the function * @param {...*} args Arguments for function */ invokeGuardedCallback: function(name, func, context, a, b, c, d, e, f) { invokeGuardedCallback.apply(ReactErrorUtils, arguments); }, /** * Same as invokeGuardedCallback, but instead of returning an error, it stores * it in a global so it can be rethrown by `rethrowCaughtError` later. * TODO: See if _caughtError and _rethrowError can be unified. * * @param {String} name of the guard to use for logging or debugging * @param {Function} func The function to invoke * @param {*} context The context to use when calling the function * @param {...*} args Arguments for function */ invokeGuardedCallbackAndCatchFirstError: function( name, func, context, a, b, c, d, e, f ) { ReactErrorUtils.invokeGuardedCallback.apply(this, arguments); if (ReactErrorUtils.hasCaughtError()) { var error = ReactErrorUtils.clearCaughtError(); if (!ReactErrorUtils._hasRethrowError) { ReactErrorUtils._hasRethrowError = true; ReactErrorUtils._rethrowError = error; } } }, /** * During execution of guarded functions we will capture the first error which * we will rethrow to be handled by the top level error handler. */ rethrowCaughtError: function() { return rethrowCaughtError.apply(ReactErrorUtils, arguments); }, hasCaughtError: function() { return ReactErrorUtils._hasCaughtError; }, clearCaughtError: function() { if (ReactErrorUtils._hasCaughtError) { var error = ReactErrorUtils._caughtError; ReactErrorUtils._caughtError = null; ReactErrorUtils._hasCaughtError = false; return error; } else { invariant( false, "clearCaughtError was called but no error was captured. This error " + "is likely caused by a bug in React. Please file an issue." ); } } }; var invokeGuardedCallback = function(name, func, context, a, b, c, d, e, f) { ReactErrorUtils._hasCaughtError = false; ReactErrorUtils._caughtError = null; var funcArgs = Array.prototype.slice.call(arguments, 3); try { func.apply(context, funcArgs); } catch (error) { ReactErrorUtils._caughtError = error; ReactErrorUtils._hasCaughtError = true; } }; { // In DEV mode, we swap out invokeGuardedCallback for a special version // that plays more nicely with the browser's DevTools. The idea is to preserve // "Pause on exceptions" behavior. Because React wraps all user-provided // functions in invokeGuardedCallback, and the production version of // invokeGuardedCallback uses a try-catch, all user exceptions are treated // like caught exceptions, and the DevTools won't pause unless the developer // takes the extra step of enabling pause on caught exceptions. This is // untintuitive, though, because even though React has caught the error, from // the developer's perspective, the error is uncaught. // // To preserve the expected "Pause on exceptions" behavior, we don't use a // try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake // DOM node, and call the user-provided callback from inside an event handler // for that fake event. If the callback throws, the error is "captured" using // a global event handler. But because the error happens in a different // event loop context, it does not interrupt the normal program flow. // Effectively, this gives us try-catch behavior without actually using // try-catch. Neat! // Check that the browser supports the APIs we need to implement our special // DEV version of invokeGuardedCallback if ( typeof window !== "undefined" && typeof window.dispatchEvent === "function" && typeof document !== "undefined" && typeof document.createEvent === "function" ) { var fakeNode = document.createElement("react"); var invokeGuardedCallbackDev = function( name, func, context, a, b, c, d, e, f ) { // If document doesn't exist we know for sure we will crash in this method // when we call document.createEvent(). However this can cause confusing // errors: https://github.com/facebookincubator/create-react-app/issues/3482 // So we preemptively throw with a better message instead. invariant( typeof document !== "undefined", "The `document` global was defined when React was initialized, but is not " + "defined anymore. This can happen in a test environment if a component " + "schedules an update from an asynchronous callback, but the test has already " + "finished running. To solve this, you can either unmount the component at " + "the end of your test (and ensure that any asynchronous operations get " + "canceled in `componentWillUnmount`), or you can change the test itself " + "to be asynchronous." ); var evt = document.createEvent("Event"); // Keeps track of whether the user-provided callback threw an error. We // set this to true at the beginning, then set it to false right after // calling the function. If the function errors, `didError` will never be // set to false. This strategy works even if the browser is flaky and // fails to call our global error handler, because it doesn't rely on // the error event at all. var didError = true; // Create an event handler for our fake event. We will synchronously // dispatch our fake event using `dispatchEvent`. Inside the handler, we // call the user-provided callback. var funcArgs = Array.prototype.slice.call(arguments, 3); function callCallback() { // We immediately remove the callback from event listeners so that // nested `invokeGuardedCallback` calls do not clash. Otherwise, a // nested call would trigger the fake event handlers of any call higher // in the stack. fakeNode.removeEventListener(evtType, callCallback, false); func.apply(context, funcArgs); didError = false; } // Create a global error event handler. We use this to capture the value // that was thrown. It's possible that this error handler will fire more // than once; for example, if non-React code also calls `dispatchEvent` // and a handler for that event throws. We should be resilient to most of // those cases. Even if our error event handler fires more than once, the // last error event is always used. If the callback actually does error, // we know that the last error event is the correct one, because it's not // possible for anything else to have happened in between our callback // erroring and the code that follows the `dispatchEvent` call below. If // the callback doesn't error, but the error event was fired, we know to // ignore it because `didError` will be false, as described above. var error = void 0; // Use this to track whether the error event is ever called. var didSetError = false; var isCrossOriginError = false; function onError(event) { error = event.error; didSetError = true; if (error === null && event.colno === 0 && event.lineno === 0) { isCrossOriginError = true; } } // Create a fake event type. var evtType = "react-" + (name ? name : "invokeguardedcallback"); // Attach our event handlers window.addEventListener("error", onError); fakeNode.addEventListener(evtType, callCallback, false); // Synchronously dispatch our fake event. If the user-provided function // errors, it will trigger our global error handler. evt.initEvent(evtType, false, false); fakeNode.dispatchEvent(evt); if (didError) { if (!didSetError) { // The callback errored, but the error event never fired. error = new Error( "An error was thrown inside one of your components, but React " + "doesn't know what it was. This is likely due to browser " + 'flakiness. React does its best to preserve the "Pause on ' + 'exceptions" behavior of the DevTools, which requires some ' + "DEV-mode only tricks. It's possible that these don't work in " + "your browser. Try triggering the error in production mode, " + "or switching to a modern browser. If you suspect that this is " + "actually an issue with React, please file an issue." ); } else if (isCrossOriginError) { error = new Error( "A cross-origin error was thrown. React doesn't have access to " + "the actual error object in development. " + "See https://fb.me/react-crossorigin-error for more information." ); } ReactErrorUtils._hasCaughtError = true; ReactErrorUtils._caughtError = error; } else { ReactErrorUtils._hasCaughtError = false; ReactErrorUtils._caughtError = null; } // Remove our event listeners window.removeEventListener("error", onError); }; invokeGuardedCallback = invokeGuardedCallbackDev; } } var rethrowCaughtError = function() { if (ReactErrorUtils._hasRethrowError) { var error = ReactErrorUtils._rethrowError; ReactErrorUtils._rethrowError = null; ReactErrorUtils._hasRethrowError = false; throw error; } }; /** * Injectable ordering of event plugins. */ var eventPluginOrder = null; /** * Injectable mapping from names to event plugin modules. */ var namesToPlugins = {}; /** * Recomputes the plugin list using the injected plugins and plugin ordering. * * @private */ function recomputePluginOrdering() { if (!eventPluginOrder) { // Wait until an `eventPluginOrder` is injected. return; } for (var pluginName in namesToPlugins) { var pluginModule = namesToPlugins[pluginName]; var pluginIndex = eventPluginOrder.indexOf(pluginName); invariant( pluginIndex > -1, "EventPluginRegistry: Cannot inject event plugins that do not exist in " + "the plugin ordering, `%s`.", pluginName ); if (plugins[pluginIndex]) { continue; } invariant( pluginModule.extractEvents, "EventPluginRegistry: Event plugins must implement an `extractEvents` " + "method, but `%s` does not.", pluginName ); plugins[pluginIndex] = pluginModule; var publishedEvents = pluginModule.eventTypes; for (var eventName in publishedEvents) { invariant( publishEventForPlugin( publishedEvents[eventName], pluginModule, eventName ), "EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.", eventName, pluginName ); } } } /** * Publishes an event so that it can be dispatched by the supplied plugin. * * @param {object} dispatchConfig Dispatch configuration for the event. * @param {object} PluginModule Plugin publishing the event. * @return {boolean} True if the event was successfully published. * @private */ function publishEventForPlugin(dispatchConfig, pluginModule, eventName) { invariant( !eventNameDispatchConfigs.hasOwnProperty(eventName), "EventPluginHub: More than one plugin attempted to publish the same " + "event name, `%s`.", eventName ); eventNameDispatchConfigs[eventName] = dispatchConfig; var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames; if (phasedRegistrationNames) { for (var phaseName in phasedRegistrationNames) { if (phasedRegistrationNames.hasOwnProperty(phaseName)) { var phasedRegistrationName = phasedRegistrationNames[phaseName]; publishRegistrationName( phasedRegistrationName, pluginModule, eventName ); } } return true; } else if (dispatchConfig.registrationName) { publishRegistrationName( dispatchConfig.registrationName, pluginModule, eventName ); return true; } return false; } /** * Publishes a registration name that is used to identify dispatched events. * * @param {string} registrationName Registration name to add. * @param {object} PluginModule Plugin publishing the event. * @private */ function publishRegistrationName(registrationName, pluginModule, eventName) { invariant( !registrationNameModules[registrationName], "EventPluginHub: More than one plugin attempted to publish the same " + "registration name, `%s`.", registrationName ); registrationNameModules[registrationName] = pluginModule; registrationNameDependencies[registrationName] = pluginModule.eventTypes[eventName].dependencies; { var lowerCasedName = registrationName.toLowerCase(); } } /** * Registers plugins so that they can extract and dispatch events. * * @see {EventPluginHub} */ /** * Ordered list of injected plugins. */ var plugins = []; /** * Mapping from event name to dispatch config */ var eventNameDispatchConfigs = {}; /** * Mapping from registration name to plugin module */ var registrationNameModules = {}; /** * Mapping from registration name to event name */ var registrationNameDependencies = {}; /** * Mapping from lowercase registration names to the properly cased version, * used to warn in the case of missing event handlers. Available * only in true. * @type {Object} */ // Trust the developer to only use possibleRegistrationNames in true /** * Injects an ordering of plugins (by plugin name). This allows the ordering * to be decoupled from injection of the actual plugins so that ordering is * always deterministic regardless of packaging, on-the-fly injection, etc. * * @param {array} InjectedEventPluginOrder * @internal * @see {EventPluginHub.injection.injectEventPluginOrder} */ function injectEventPluginOrder(injectedEventPluginOrder) { invariant( !eventPluginOrder, "EventPluginRegistry: Cannot inject event plugin ordering more than " + "once. You are likely trying to load more than one copy of React." ); // Clone the ordering so it cannot be dynamically mutated. eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder); recomputePluginOrdering(); } /** * Injects plugins to be used by `EventPluginHub`. The plugin names must be * in the ordering injected by `injectEventPluginOrder`. * * Plugins can be injected as part of page initialization or on-the-fly. * * @param {object} injectedNamesToPlugins Map from names to plugin modules. * @internal * @see {EventPluginHub.injection.injectEventPluginsByName} */ function injectEventPluginsByName(injectedNamesToPlugins) { var isOrderingDirty = false; for (var pluginName in injectedNamesToPlugins) { if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) { continue; } var pluginModule = injectedNamesToPlugins[pluginName]; if ( !namesToPlugins.hasOwnProperty(pluginName) || namesToPlugins[pluginName] !== pluginModule ) { invariant( !namesToPlugins[pluginName], "EventPluginRegistry: Cannot inject two different event plugins " + "using the same name, `%s`.", pluginName ); namesToPlugins[pluginName] = pluginModule; isOrderingDirty = true; } } if (isOrderingDirty) { recomputePluginOrdering(); } } var getFiberCurrentPropsFromNode = null; var getInstanceFromNode = null; var getNodeFromInstance = null; var injection$1 = { injectComponentTree: function(Injected) { getFiberCurrentPropsFromNode = Injected.getFiberCurrentPropsFromNode; getInstanceFromNode = Injected.getInstanceFromNode; getNodeFromInstance = Injected.getNodeFromInstance; { warning( getNodeFromInstance && getInstanceFromNode, "EventPluginUtils.injection.injectComponentTree(...): Injected " + "module is missing getNodeFromInstance or getInstanceFromNode." ); } } }; function isEndish(topLevelType) { return ( topLevelType === "topMouseUp" || topLevelType === "topTouchEnd" || topLevelType === "topTouchCancel" ); } function isMoveish(topLevelType) { return topLevelType === "topMouseMove" || topLevelType === "topTouchMove"; } function isStartish(topLevelType) { return topLevelType === "topMouseDown" || topLevelType === "topTouchStart"; } var validateEventDispatches; { validateEventDispatches = function(event) { var dispatchListeners = event._dispatchListeners; var dispatchInstances = event._dispatchInstances; var listenersIsArr = Array.isArray(dispatchListeners); var listenersLen = listenersIsArr ? dispatchListeners.length : dispatchListeners ? 1 : 0; var instancesIsArr = Array.isArray(dispatchInstances); var instancesLen = instancesIsArr ? dispatchInstances.length : dispatchInstances ? 1 : 0; warning( instancesIsArr === listenersIsArr && instancesLen === listenersLen, "EventPluginUtils: Invalid `event`." ); }; } /** * Dispatch the event to the listener. * @param {SyntheticEvent} event SyntheticEvent to handle * @param {boolean} simulated If the event is simulated (changes exn behavior) * @param {function} listener Application-level callback * @param {*} inst Internal component instance */ function executeDispatch(event, simulated, listener, inst) { var type = event.type || "unknown-event"; event.currentTarget = getNodeFromInstance(inst); ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError( type, listener, undefined, event ); event.currentTarget = null; } /** * Standard/simple iteration through an event's collected dispatches. */ function executeDispatchesInOrder(event, simulated) { var dispatchListeners = event._dispatchListeners; var dispatchInstances = event._dispatchInstances; { validateEventDispatches(event); } if (Array.isArray(dispatchListeners)) { for (var i = 0; i < dispatchListeners.length; i++) { if (event.isPropagationStopped()) { break; } // Listeners and Instances are two parallel arrays that are always in sync. executeDispatch( event, simulated, dispatchListeners[i], dispatchInstances[i] ); } } else if (dispatchListeners) { executeDispatch(event, simulated, dispatchListeners, dispatchInstances); } event._dispatchListeners = null; event._dispatchInstances = null; } /** * Standard/simple iteration through an event's collected dispatches, but stops * at the first dispatch execution returning true, and returns that id. * * @return {?string} id of the first dispatch execution who's listener returns * true, or null if no listener returned true. */ function executeDispatchesInOrderStopAtTrueImpl(event) { var dispatchListeners = event._dispatchListeners; var dispatchInstances = event._dispatchInstances; { validateEventDispatches(event); } if (Array.isArray(dispatchListeners)) { for (var i = 0; i < dispatchListeners.length; i++) { if (event.isPropagationStopped()) { break; } // Listeners and Instances are two parallel arrays that are always in sync. if (dispatchListeners[i](event, dispatchInstances[i])) { return dispatchInstances[i]; } } } else if (dispatchListeners) { if (dispatchListeners(event, dispatchInstances)) { return dispatchInstances; } } return null; } /** * @see executeDispatchesInOrderStopAtTrueImpl */ function executeDispatchesInOrderStopAtTrue(event) { var ret = executeDispatchesInOrderStopAtTrueImpl(event); event._dispatchInstances = null; event._dispatchListeners = null; return ret; } /** * Execution of a "direct" dispatch - there must be at most one dispatch * accumulated on the event or it is considered an error. It doesn't really make * sense for an event with multiple dispatches (bubbled) to keep track of the * return values at each dispatch execution, but it does tend to make sense when * dealing with "direct" dispatches. * * @return {*} The return value of executing the single dispatch. */ function executeDirectDispatch(event) { { validateEventDispatches(event); } var dispatchListener = event._dispatchListeners; var dispatchInstance = event._dispatchInstances; invariant( !Array.isArray(dispatchListener), "executeDirectDispatch(...): Invalid `event`." ); event.currentTarget = dispatchListener ? getNodeFromInstance(dispatchInstance) : null; var res = dispatchListener ? dispatchListener(event) : null; event.currentTarget = null; event._dispatchListeners = null; event._dispatchInstances = null; return res; } /** * @param {SyntheticEvent} event * @return {boolean} True iff number of dispatches accumulated is greater than 0. */ function hasDispatches(event) { return !!event._dispatchListeners; } /** * Accumulates items that must not be null or undefined into the first one. This * is used to conserve memory by avoiding array allocations, and thus sacrifices * API cleanness. Since `current` can be null before being passed in and not * null after this function, make sure to assign it back to `current`: * * `a = accumulateInto(a, b);` * * This API should be sparingly used. Try `accumulate` for something cleaner. * * @return {*|array<*>} An accumulation of items. */ function accumulateInto(current, next) { invariant( next != null, "accumulateInto(...): Accumulated items must not be null or undefined." ); if (current == null) { return next; } // Both are not empty. Warning: Never call x.concat(y) when you are not // certain that x is an Array (x could be a string with concat method). if (Array.isArray(current)) { if (Array.isArray(next)) { current.push.apply(current, next); return current; } current.push(next); return current; } if (Array.isArray(next)) { // A bit too dangerous to mutate `next`. return [current].concat(next); } return [current, next]; } /** * @param {array} arr an "accumulation" of items which is either an Array or * a single item. Useful when paired with the `accumulate` module. This is a * simple utility that allows us to reason about a collection of items, but * handling the case when there is exactly one item (and we do not need to * allocate an array). * @param {function} cb Callback invoked with each element or a collection. * @param {?} [scope] Scope used as `this` in a callback. */ function forEachAccumulated(arr, cb, scope) { if (Array.isArray(arr)) { arr.forEach(cb, scope); } else if (arr) { cb.call(scope, arr); } } /** * Internal queue of events that have accumulated their dispatches and are * waiting to have their dispatches executed. */ var eventQueue = null; /** * Dispatches an event and releases it back into the pool, unless persistent. * * @param {?object} event Synthetic event to be dispatched. * @param {boolean} simulated If the event is simulated (changes exn behavior) * @private */ var executeDispatchesAndRelease = function(event, simulated) { if (event) { executeDispatchesInOrder(event, simulated); if (!event.isPersistent()) { event.constructor.release(event); } } }; var executeDispatchesAndReleaseSimulated = function(e) { return executeDispatchesAndRelease(e, true); }; var executeDispatchesAndReleaseTopLevel = function(e) { return executeDispatchesAndRelease(e, false); }; function isInteractive(tag) { return ( tag === "button" || tag === "input" || tag === "select" || tag === "textarea" ); } function shouldPreventMouseEvent(name, type, props) { switch (name) { case "onClick": case "onClickCapture": case "onDoubleClick": case "onDoubleClickCapture": case "onMouseDown": case "onMouseDownCapture": case "onMouseMove": case "onMouseMoveCapture": case "onMouseUp": case "onMouseUpCapture": return !!(props.disabled && isInteractive(type)); default: return false; } } /** * This is a unified interface for event plugins to be installed and configured. * * Event plugins can implement the following properties: * * `extractEvents` {function(string, DOMEventTarget, string, object): *} * Required. When a top-level event is fired, this method is expected to * extract synthetic events that will in turn be queued and dispatched. * * `eventTypes` {object} * Optional, plugins that fire events must publish a mapping of registration * names that are used to register listeners. Values of this mapping must * be objects that contain `registrationName` or `phasedRegistrationNames`. * * `executeDispatch` {function(object, function, string)} * Optional, allows plugins to override how an event gets dispatched. By * default, the listener is simply invoked. * * Each plugin that is injected into `EventsPluginHub` is immediately operable. * * @public */ /** * Methods for injecting dependencies. */ var injection = { /** * @param {array} InjectedEventPluginOrder * @public */ injectEventPluginOrder: injectEventPluginOrder, /** * @param {object} injectedNamesToPlugins Map from names to plugin modules. */ injectEventPluginsByName: injectEventPluginsByName }; /** * @param {object} inst The instance, which is the source of events. * @param {string} registrationName Name of listener (e.g. `onClick`). * @return {?function} The stored callback. */ function getListener(inst, registrationName) { var listener; // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not // live here; needs to be moved to a better place soon var stateNode = inst.stateNode; if (!stateNode) { // Work in progress (ex: onload events in incremental mode). return null; } var props = getFiberCurrentPropsFromNode(stateNode); if (!props) { // Work in progress. return null; } listener = props[registrationName]; if (shouldPreventMouseEvent(registrationName, inst.type, props)) { return null; } invariant( !listener || typeof listener === "function", "Expected `%s` listener to be a function, instead got a value of `%s` type.", registrationName, typeof listener ); return listener; } /** * Allows registered plugins an opportunity to extract events from top-level * native browser events. * * @return {*} An accumulation of synthetic events. * @internal */ function extractEvents( topLevelType, targetInst, nativeEvent, nativeEventTarget ) { var events; for (var i = 0; i < plugins.length; i++) { // Not every plugin in the ordering may be loaded at runtime. var possiblePlugin = plugins[i]; if (possiblePlugin) { var extractedEvents = possiblePlugin.extractEvents( topLevelType, targetInst, nativeEvent, nativeEventTarget ); if (extractedEvents) { events = accumulateInto(events, extractedEvents); } } } return events; } /** * Enqueues a synthetic event that should be dispatched when * `processEventQueue` is invoked. * * @param {*} events An accumulation of synthetic events. * @internal */ function enqueueEvents(events) { if (events) { eventQueue = accumulateInto(eventQueue, events); } } /** * Dispatches all synthetic events on the event queue. * * @internal */ function processEventQueue(simulated) { // Set `eventQueue` to null before processing it so that we can tell if more // events get enqueued while processing. var processingEventQueue = eventQueue; eventQueue = null; if (!processingEventQueue) { return; } if (simulated) { forEachAccumulated( processingEventQueue, executeDispatchesAndReleaseSimulated ); } else { forEachAccumulated( processingEventQueue, executeDispatchesAndReleaseTopLevel ); } invariant( !eventQueue, "processEventQueue(): Additional events were enqueued while processing " + "an event queue. Support for this has not yet been implemented." ); // This would be a good time to rethrow if any of the event handlers threw. ReactErrorUtils.rethrowCaughtError(); } var IndeterminateComponent = 0; // Before we know whether it is functional or class var FunctionalComponent = 1; var ClassComponent = 2; var HostRoot = 3; // Root of a host tree. Could be nested inside another node. var HostPortal = 4; // A subtree. Could be an entry point to a different renderer. var HostComponent = 5; var HostText = 6; var CallComponent = 7; var CallHandlerPhase = 8; var ReturnComponent = 9; var Fragment = 10; function getParent(inst) { do { inst = inst["return"]; // TODO: If this is a HostRoot we might want to bail out. // That is depending on if we want nested subtrees (layers) to bubble // events to their parent. We could also go through parentNode on the // host node but that wouldn't work for React Native and doesn't let us // do the portal feature. } while (inst && inst.tag !== HostComponent); if (inst) { return inst; } return null; } /** * Return the lowest common ancestor of A and B, or null if they are in * different trees. */ function getLowestCommonAncestor(instA, instB) { var depthA = 0; for (var tempA = instA; tempA; tempA = getParent(tempA)) { depthA++; } var depthB = 0; for (var tempB = instB; tempB; tempB = getParent(tempB)) { depthB++; } // If A is deeper, crawl up. while (depthA - depthB > 0) { instA = getParent(instA); depthA--; } // If B is deeper, crawl up. while (depthB - depthA > 0) { instB = getParent(instB); depthB--; } // Walk in lockstep until we find a match. var depth = depthA; while (depth--) { if (instA === instB || instA === instB.alternate) { return instA; } instA = getParent(instA); instB = getParent(instB); } return null; } /** * Return if A is an ancestor of B. */ function isAncestor(instA, instB) { while (instB) { if (instA === instB || instA === instB.alternate) { return true; } instB = getParent(instB); } return false; } /** * Return the parent instance of the passed-in instance. */ function getParentInstance(inst) { return getParent(inst); } /** * Simulates the traversal of a two-phase, capture/bubble event dispatch. */ function traverseTwoPhase(inst, fn, arg) { var path = []; while (inst) { path.push(inst); inst = getParent(inst); } var i; for (i = path.length; i-- > 0; ) { fn(path[i], "captured", arg); } for (i = 0; i < path.length; i++) { fn(path[i], "bubbled", arg); } } /** * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that * should would receive a `mouseEnter` or `mouseLeave` event. * * Does not invoke the callback on the nearest common ancestor because nothing * "entered" or "left" that element. */ /** * Some event types have a notion of different registration names for different * "phases" of propagation. This finds listeners by a given phase. */ function listenerAtPhase(inst, event, propagationPhase) { var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase]; return getListener(inst, registrationName); } /** * A small set of propagation patterns, each of which will accept a small amount * of information, and generate a set of "dispatch ready event objects" - which * are sets of events that have already been annotated with a set of dispatched * listener functions/ids. The API is designed this way to discourage these * propagation strategies from actually executing the dispatches, since we * always want to collect the entire set of dispatches before executing even a * single one. */ /** * Tags a `SyntheticEvent` with dispatched listeners. Creating this function * here, allows us to not have to bind or create functions for each event. * Mutating the event's members allows us to not have to create a wrapping * "dispatch" object that pairs the event with the listener. */ function accumulateDirectionalDispatches(inst, phase, event) { { warning(inst, "Dispatching inst must not be null"); } var listener = listenerAtPhase(inst, event, phase); if (listener) { event._dispatchListeners = accumulateInto( event._dispatchListeners, listener ); event._dispatchInstances = accumulateInto(event._dispatchInstances, inst); } } /** * Collect dispatches (must be entirely collected before dispatching - see unit * tests). Lazily allocate the array to conserve memory. We must loop through * each event and perform the traversal for each one. We cannot perform a * single traversal for the entire collection of events because each event may * have a different target. */ function accumulateTwoPhaseDispatchesSingle(event) { if (event && event.dispatchConfig.phasedRegistrationNames) { traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event); } } /** * Same as `accumulateTwoPhaseDispatchesSingle`, but skips over the targetID. */ function accumulateTwoPhaseDispatchesSingleSkipTarget(event) { if (event && event.dispatchConfig.phasedRegistrationNames) { var targetInst = event._targetInst; var parentInst = targetInst ? getParentInstance(targetInst) : null; traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event); } } /** * Accumulates without regard to direction, does not look for phased * registration names. Same as `accumulateDirectDispatchesSingle` but without * requiring that the `dispatchMarker` be the same as the dispatched ID. */ function accumulateDispatches(inst, ignoredDirection, event) { if (inst && event && event.dispatchConfig.registrationName) { var registrationName = event.dispatchConfig.registrationName; var listener = getListener(inst, registrationName); if (listener) { event._dispatchListeners = accumulateInto( event._dispatchListeners, listener ); event._dispatchInstances = accumulateInto(event._dispatchInstances, inst); } } } /** * Accumulates dispatches on an `SyntheticEvent`, but only for the * `dispatchMarker`. * @param {SyntheticEvent} event */ function accumulateDirectDispatchesSingle(event) { if (event && event.dispatchConfig.registrationName) { accumulateDispatches(event._targetInst, null, event); } } function accumulateTwoPhaseDispatches(events) { forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle); } function accumulateTwoPhaseDispatchesSkipTarget(events) { forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget); } function accumulateDirectDispatches(events) { forEachAccumulated(events, accumulateDirectDispatchesSingle); } /* eslint valid-typeof: 0 */ var didWarnForAddedNewProperty = false; var isProxySupported = typeof Proxy === "function"; var EVENT_POOL_SIZE = 10; var shouldBeReleasedProperties = [ "dispatchConfig", "_targetInst", "nativeEvent", "isDefaultPrevented", "isPropagationStopped", "_dispatchListeners", "_dispatchInstances" ]; /** * @interface Event * @see http://www.w3.org/TR/DOM-Level-3-Events/ */ var EventInterface = { type: null, target: null, // currentTarget is set when dispatching; no use in copying it here currentTarget: emptyFunction.thatReturnsNull, eventPhase: null, bubbles: null, cancelable: null, timeStamp: function(event) { return event.timeStamp || Date.now(); }, defaultPrevented: null, isTrusted: null }; /** * Synthetic events are dispatched by event plugins, typically in response to a * top-level event delegation handler. * * These systems should generally use pooling to reduce the frequency of garbage * collection. The system should check `isPersistent` to determine whether the * event should be released into the pool after being dispatched. Users that * need a persisted event should invoke `persist`. * * Synthetic events (and subclasses) implement the DOM Level 3 Events API by * normalizing browser quirks. Subclasses do not necessarily have to implement a * DOM interface; custom application-specific events can also subclass this. * * @param {object} dispatchConfig Configuration used to dispatch this event. * @param {*} targetInst Marker identifying the event target. * @param {object} nativeEvent Native browser event. * @param {DOMEventTarget} nativeEventTarget Target node. */ function SyntheticEvent( dispatchConfig, targetInst, nativeEvent, nativeEventTarget ) { { // these have a getter/setter for warnings delete this.nativeEvent; delete this.preventDefault; delete this.stopPropagation; } this.dispatchConfig = dispatchConfig; this._targetInst = targetInst; this.nativeEvent = nativeEvent; var Interface = this.constructor.Interface; for (var propName in Interface) { if (!Interface.hasOwnProperty(propName)) { continue; } { delete this[propName]; // this has a getter/setter for warnings } var normalize = Interface[propName]; if (normalize) { this[propName] = normalize(nativeEvent); } else { if (propName === "target") { this.target = nativeEventTarget; } else { this[propName] = nativeEvent[propName]; } } } var defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false; if (defaultPrevented) { this.isDefaultPrevented = emptyFunction.thatReturnsTrue; } else { this.isDefaultPrevented = emptyFunction.thatReturnsFalse; } this.isPropagationStopped = emptyFunction.thatReturnsFalse; return this; } Object.assign(SyntheticEvent.prototype, { preventDefault: function() { this.defaultPrevented = true; var event = this.nativeEvent; if (!event) { return; } if (event.preventDefault) { event.preventDefault(); } else if (typeof event.returnValue !== "unknown") { event.returnValue = false; } this.isDefaultPrevented = emptyFunction.thatReturnsTrue; }, stopPropagation: function() { var event = this.nativeEvent; if (!event) { return; } if (event.stopPropagation) { event.stopPropagation(); } else if (typeof event.cancelBubble !== "unknown") { // The ChangeEventPlugin registers a "propertychange" event for // IE. This event does not support bubbling or cancelling, and // any references to cancelBubble throw "Member not found". A // typeof check of "unknown" circumvents this issue (and is also // IE specific). event.cancelBubble = true; } this.isPropagationStopped = emptyFunction.thatReturnsTrue; }, /** * We release all dispatched `SyntheticEvent`s after each event loop, adding * them back into the pool. This allows a way to hold onto a reference that * won't be added back into the pool. */ persist: function() { this.isPersistent = emptyFunction.thatReturnsTrue; }, /** * Checks if this event should be released back into the pool. * * @return {boolean} True if this should not be released, false otherwise. */ isPersistent: emptyFunction.thatReturnsFalse, /** * `PooledClass` looks for `destructor` on each instance it releases. */ destructor: function() { var Interface = this.constructor.Interface; for (var propName in Interface) { { Object.defineProperty( this, propName, getPooledWarningPropertyDefinition(propName, Interface[propName]) ); } } for (var i = 0; i < shouldBeReleasedProperties.length; i++) { this[shouldBeReleasedProperties[i]] = null; } { Object.defineProperty( this, "nativeEvent", getPooledWarningPropertyDefinition("nativeEvent", null) ); Object.defineProperty( this, "preventDefault", getPooledWarningPropertyDefinition("preventDefault", emptyFunction) ); Object.defineProperty( this, "stopPropagation", getPooledWarningPropertyDefinition("stopPropagation", emptyFunction) ); } } }); SyntheticEvent.Interface = EventInterface; /** * Helper to reduce boilerplate when creating subclasses. * * @param {function} Class * @param {?object} Interface */ SyntheticEvent.augmentClass = function(Class, Interface) { var Super = this; var E = function() {}; E.prototype = Super.prototype; var prototype = new E(); Object.assign(prototype, Class.prototype); Class.prototype = prototype; Class.prototype.constructor = Class; Class.Interface = Object.assign({}, Super.Interface, Interface); Class.augmentClass = Super.augmentClass; addEventPoolingTo(Class); }; /** Proxying after everything set on SyntheticEvent * to resolve Proxy issue on some WebKit browsers * in which some Event properties are set to undefined (GH#10010) */ { if (isProxySupported) { /*eslint-disable no-func-assign */ SyntheticEvent = new Proxy(SyntheticEvent, { construct: function(target, args) { return this.apply(target, Object.create(target.prototype), args); }, apply: function(constructor, that, args) { return new Proxy(constructor.apply(that, args), { set: function(target, prop, value) { if ( prop !== "isPersistent" && !target.constructor.Interface.hasOwnProperty(prop) && shouldBeReleasedProperties.indexOf(prop) === -1 ) { warning( didWarnForAddedNewProperty || target.isPersistent(), "This synthetic event is reused for performance reasons. If you're " + "seeing this, you're adding a new property in the synthetic event object. " + "The property is never released. See " + "https://fb.me/react-event-pooling for more information." ); didWarnForAddedNewProperty = true; } target[prop] = value; return true; } }); } }); /*eslint-enable no-func-assign */ } } addEventPoolingTo(SyntheticEvent); /** * Helper to nullify syntheticEvent instance properties when destructing * * @param {String} propName * @param {?object} getVal * @return {object} defineProperty object */ function getPooledWarningPropertyDefinition(propName, getVal) { var isFunction = typeof getVal === "function"; return { configurable: true, set: set, get: get }; function set(val) { var action = isFunction ? "setting the method" : "setting the property"; warn(action, "This is effectively a no-op"); return val; } function get() { var action = isFunction ? "accessing the method" : "accessing the property"; var result = isFunction ? "This is a no-op function" : "This is set to null"; warn(action, result); return getVal; } function warn(action, result) { var warningCondition = false; warning( warningCondition, "This synthetic event is reused for performance reasons. If you're seeing this, " + "you're %s `%s` on a released/nullified synthetic event. %s. " + "If you must keep the original synthetic event around, use event.persist(). " + "See https://fb.me/react-event-pooling for more information.", action, propName, result ); } } function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) { var EventConstructor = this; if (EventConstructor.eventPool.length) { var instance = EventConstructor.eventPool.pop(); EventConstructor.call( instance, dispatchConfig, targetInst, nativeEvent, nativeInst ); return instance; } return new EventConstructor( dispatchConfig, targetInst, nativeEvent, nativeInst ); } function releasePooledEvent(event) { var EventConstructor = this; invariant( event instanceof EventConstructor, "Trying to release an event instance into a pool of a different type." ); event.destructor(); if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) { EventConstructor.eventPool.push(event); } } function addEventPoolingTo(EventConstructor) { EventConstructor.eventPool = []; EventConstructor.getPooled = getPooledEvent; EventConstructor.release = releasePooledEvent; } var SyntheticEvent$1 = SyntheticEvent; /** * `touchHistory` isn't actually on the native event, but putting it in the * interface will ensure that it is cleaned up when pooled/destroyed. The * `ResponderEventPlugin` will populate it appropriately. */ var ResponderEventInterface = { touchHistory: function(nativeEvent) { return null; // Actually doesn't even look at the native event. } }; /** * @param {object} dispatchConfig Configuration used to dispatch this event. * @param {string} dispatchMarker Marker identifying the event target. * @param {object} nativeEvent Native event. * @extends {SyntheticEvent} */ function ResponderSyntheticEvent( dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget ) { return SyntheticEvent$1.call( this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget ); } SyntheticEvent$1.augmentClass(ResponderSyntheticEvent, ResponderEventInterface); /** * Tracks the position and time of each active touch by `touch.identifier`. We * should typically only see IDs in the range of 1-20 because IDs get recycled * when touches end and start again. */ var MAX_TOUCH_BANK = 20; var touchBank = []; var touchHistory = { touchBank: touchBank, numberActiveTouches: 0, // If there is only one active touch, we remember its location. This prevents // us having to loop through all of the touches all the time in the most // common case. indexOfSingleActiveTouch: -1, mostRecentTimeStamp: 0 }; function timestampForTouch(touch) { // The legacy internal implementation provides "timeStamp", which has been // renamed to "timestamp". Let both work for now while we iron it out // TODO (evv): rename timeStamp to timestamp in internal code return touch.timeStamp || touch.timestamp; } /** * TODO: Instead of making gestures recompute filtered velocity, we could * include a built in velocity computation that can be reused globally. */ function createTouchRecord(touch) { return { touchActive: true, startPageX: touch.pageX, startPageY: touch.pageY, startTimeStamp: timestampForTouch(touch), currentPageX: touch.pageX, currentPageY: touch.pageY, currentTimeStamp: timestampForTouch(touch), previousPageX: touch.pageX, previousPageY: touch.pageY, previousTimeStamp: timestampForTouch(touch) }; } function resetTouchRecord(touchRecord, touch) { touchRecord.touchActive = true; touchRecord.startPageX = touch.pageX; touchRecord.startPageY = touch.pageY; touchRecord.startTimeStamp = timestampForTouch(touch); touchRecord.currentPageX = touch.pageX; touchRecord.currentPageY = touch.pageY; touchRecord.currentTimeStamp = timestampForTouch(touch); touchRecord.previousPageX = touch.pageX; touchRecord.previousPageY = touch.pageY; touchRecord.previousTimeStamp = timestampForTouch(touch); } function getTouchIdentifier(_ref) { var identifier = _ref.identifier; invariant(identifier != null, "Touch object is missing identifier."); { warning( identifier <= MAX_TOUCH_BANK, "Touch identifier %s is greater than m