import SimpleEventPlugin from './SimpleEventPlugin';
import {
DiscreteEvent,
UserBlockingEvent,
ContinuousEvent,
} from '../../shared/ReactTypes';
import { PLUGIN_EVENT_SYSTEM } from '../../events/EventSystemFlags';
import { discreteUpdates, batchedEventUpdates } from './ReactGenericBatching';
import getEventTarget from './getEventTarget';
import { getClosestInstanceFromNode } from '../ReactDOMComponentTree';
import { HostRoot } from '../../shared/ReactWorkTags';
import { getRawEventName } from './DOMTopLevelEventTypes';
import {
addEventCaptureListener,
addEventBubbleListener,
} from './EventListener';
import { runExtractedPluginEventsInBatch } from '../../events/EventPluginHub'
const { getEventPriority } = SimpleEventPlugin;
const CALLBACK_BOOKKEEPING_POOL_SIZE = 10;
const callbackBookkeepingPool = [];
export let _enabled = true;
export function setEnabled(enabled) {
_enabled = !!enabled;
}
export function isEnabled() {
return _enabled;
}
function releaseTopLevelCallbackBookKeeping(instance) {
instance.topLevelType = null;
instance.nativeEvent = null;
instance.targetInst = null;
instance.ancestors.length = 0;
if (callbackBookkeepingPool.length < CALLBACK_BOOKKEEPING_POOL_SIZE) {
callbackBookkeepingPool.push(instance);
}
}
function getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst) {
if (callbackBookkeepingPool.length) {
// å¤ç¨
const instance = callbackBookkeepingPool.pop();
instance.topLevelType = topLevelType;
instance.nativeEvent = nativeEvent;
instance.targetInst = targetInst;
return instance;
}
return {
topLevelType,
nativeEvent,
targetInst,
ancestors: [],
};
}
function findRootContainerNode(inst) {
while (inst.return) {
inst = inst.return;
}
if (inst.tag !== HostRoot) {
// This can happen if we're in a detached tree.
return null;
}
return inst.stateNode.containerInfo;
}
function handleTopLevel(bookKeeping) {
let targetInst = bookKeeping.targetInst;
let ancestor = targetInst;
do {
if (!ancestor) {
const ancestors = bookKeeping.ancestors;
ancestors.push(ancestor);
break;
}
const root = findRootContainerNode(ancestor);
if (!root) {
break;
}
bookKeeping.ancestors.push(ancestor);
ancestor = getClosestInstanceFromNode(root);
} while (ancestor);
for (let i = 0; i < bookKeeping.ancestors.length; i++) {
targetInst = bookKeeping.ancestors[i];
const eventTarget = getEventTarget(bookKeeping.nativeEvent);
const topLevelType = bookKeeping.topLevelType;
const nativeEvent = bookKeeping.nativeEvent;
runExtractedPluginEventsInBatch(
topLevelType,
targetInst,
nativeEvent,
eventTarget,
);
}
}
function dispatchEventForPluginEventSystem(
topLevelType,
eventSystemFlags,
nativeEvent,
targetInst,
) {
const bookKeeping = getTopLevelCallbackBookKeeping(
topLevelType,
nativeEvent,
targetInst,
);
try {
batchedEventUpdates(handleTopLevel, bookKeeping);
} finally {
releaseTopLevelCallbackBookKeeping(bookKeeping);
}
}
function dispatchEvent(topLevelType, eventSystemFlags, nativeEvent) {
if (!_enabled) {
return;
}
const nativeEventTarget = getEventTarget(nativeEvent);
let targetInst = getClosestInstanceFromNode(nativeEventTarget);
dispatchEventForPluginEventSystem(
topLevelType,
eventSystemFlags,
nativeEvent,
targetInst,
);
}
function dispatchDiscreteEvent(topLevelType, eventSystemFlags, nativeEvent) {
discreteUpdates(dispatchEvent, topLevelType, eventSystemFlags, nativeEvent);
}
function trapEventForPluginEventSystem(element, topLevelType, capture) {
let listener;
switch (getEventPriority(topLevelType)) {
case DiscreteEvent:
listener = dispatchDiscreteEvent.bind(
null,
topLevelType,
PLUGIN_EVENT_SYSTEM,
);
break;
case UserBlockingEvent:
case ContinuousEvent:
default:
listener = dispatchEvent.bind(
null,
topLevelType,
PLUGIN_EVENT_SYSTEM,
);
break;
}
const rawEventName = getRawEventName(topLevelType);
if (capture) {
addEventCaptureListener(element, rawEventName, listener);
} else {
addEventBubbleListener(element, rawEventName, listener);
}
}
export function trapBubbledEvent(topLevelType, element) {
trapEventForPluginEventSystem(element, topLevelType, false);
}
/**
* 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 ReactEventListener
*/
'use strict';
var EventListener = require('EventListener');
var ExecutionEnvironment = require('ExecutionEnvironment');
var PooledClass = require('PooledClass');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactGenericBatching = require('ReactGenericBatching');
var ReactTypeOfWork = require('ReactTypeOfWork');
var getEventTarget = require('getEventTarget');
var getUnboundedScrollPosition = require('getUnboundedScrollPosition');
var { HostContainer } = ReactTypeOfWork;
/**
* Find the deepest React component completely containing the root of the
* passed-in instance (for use when entire React trees are nested within each
* other). If React trees are not nested, returns null.
*/
function findRootContainerNode(inst) {
// TODO: It may be a good idea to cache this to prevent unnecessary DOM
// traversal, but caching is difficult to do correctly without using a
// mutation observer to listen for all DOM changes.
if (typeof inst.tag === 'number') {
while (inst.return) {
inst = inst.return;
}
if (inst.tag !== HostContainer) {
// This can happen if we're in a detached tree.
return null;
}
return inst.stateNode.containerInfo;
} else {
while (inst._hostParent) {
inst = inst._hostParent;
}
var rootNode = ReactDOMComponentTree.getNodeFromInstance(inst);
return rootNode.parentNode;
}
}
// Used to store ancestor hierarchy in top level callback
function TopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst) {
this.topLevelType = topLevelType;
this.nativeEvent = nativeEvent;
this.targetInst = targetInst;
this.ancestors = [];
}
Object.assign(TopLevelCallbackBookKeeping.prototype, {
destructor: function() {
this.topLevelType = null;
this.nativeEvent = null;
this.targetInst = null;
this.ancestors.length = 0;
},
});
PooledClass.addPoolingTo(
TopLevelCallbackBookKeeping,
PooledClass.threeArgumentPooler
);
function handleTopLevelImpl(bookKeeping) {
var targetInst = bookKeeping.targetInst;
// Loop through the hierarchy, in case there's any nested components.
// It's important that we build the array of ancestors before calling any
// event handlers, because event handlers can modify the DOM, leading to
// inconsistencies with ReactMount's node cache. See #1105.
var ancestor = targetInst;
do {
if (!ancestor) {
bookKeeping.ancestors.push(ancestor);
break;
}
var root = findRootContainerNode(ancestor);
if (!root) {
break;
}
bookKeeping.ancestors.push(ancestor);
ancestor = ReactDOMComponentTree.getClosestInstanceFromNode(
root
);
} while (ancestor);
for (var i = 0; i < bookKeeping.ancestors.length; i++) {
targetInst = bookKeeping.ancestors[i];
ReactEventListener._handleTopLevel(
bookKeeping.topLevelType,
targetInst,
bookKeeping.nativeEvent,
getEventTarget(bookKeeping.nativeEvent)
);
}
}
function scrollValueMonitor(cb) {
var scrollPosition = getUnboundedScrollPosition(window);
cb(scrollPosition);
}
var ReactEventListener = {
_enabled: true,
_handleTopLevel: null,
WINDOW_HANDLE: ExecutionEnvironment.canUseDOM ? window : null,
setHandleTopLevel: function(handleTopLevel) {
ReactEventListener._handleTopLevel = handleTopLevel;
},
setEnabled: function(enabled) {
ReactEventListener._enabled = !!enabled;
},
isEnabled: function() {
return ReactEventListener._enabled;
},
/**
* Traps top-level events by using event bubbling.
*
* @param {string} topLevelType Record from `EventConstants`.
* @param {string} handlerBaseName Event name (e.g. "click").
* @param {object} element Element on which to attach listener.
* @return {?object} An object with a remove function which will forcefully
* remove the listener.
* @internal
*/
trapBubbledEvent: function(topLevelType, handlerBaseName, element) {
if (!element) {
return null;
}
return EventListener.listen(
element,
handlerBaseName,
ReactEventListener.dispatchEvent.bind(null, topLevelType)
);
},
/**
* Traps a top-level event by using event capturing.
*
* @param {string} topLevelType Record from `EventConstants`.
* @param {string} handlerBaseName Event name (e.g. "click").
* @param {object} element Element on which to attach listener.
* @return {?object} An object with a remove function which will forcefully
* remove the listener.
* @internal
*/
trapCapturedEvent: function(topLevelType, handlerBaseName, element) {
if (!element) {
return null;
}
return EventListener.capture(
element,
handlerBaseName,
ReactEventListener.dispatchEvent.bind(null, topLevelType)
);
},
monitorScrollValue: function(refresh) {
var callback = scrollValueMonitor.bind(null, refresh);
EventListener.listen(window, 'scroll', callback);
},
dispatchEvent: function(topLevelType, nativeEvent) {
if (!ReactEventListener._enabled) {
return;
}
var nativeEventTarget = getEventTarget(nativeEvent);
var targetInst = ReactDOMComponentTree.getClosestInstanceFromNode(
nativeEventTarget
);
var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
topLevelType,
nativeEvent,
targetInst
);
try {
// Event queue being processed in the same cycle allows
// `preventDefault`.
ReactGenericBatching.batchedUpdates(handleTopLevelImpl, bookKeeping);
} finally {
TopLevelCallbackBookKeeping.release(bookKeeping);
}
},
};
module.exports = ReactEventListener;