277 lines
8.9 KiB
JavaScript
277 lines
8.9 KiB
JavaScript
/**
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var _prodInvariant = require('./reactProdInvariant');
|
|
|
|
var EventPluginRegistry = require('./EventPluginRegistry');
|
|
var EventPluginUtils = require('./EventPluginUtils');
|
|
var ReactErrorUtils = require('./ReactErrorUtils');
|
|
|
|
var accumulateInto = require('./accumulateInto');
|
|
var forEachAccumulated = require('./forEachAccumulated');
|
|
var invariant = require('fbjs/lib/invariant');
|
|
|
|
/**
|
|
* Internal store for event listeners
|
|
*/
|
|
var listenerBank = {};
|
|
|
|
/**
|
|
* 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) {
|
|
EventPluginUtils.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);
|
|
};
|
|
|
|
var getDictionaryKey = function (inst) {
|
|
// Prevents V8 performance issue:
|
|
// https://github.com/facebook/react/pull/7232
|
|
return '.' + inst._rootNodeID;
|
|
};
|
|
|
|
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
|
|
*/
|
|
var EventPluginHub = {
|
|
|
|
/**
|
|
* Methods for injecting dependencies.
|
|
*/
|
|
injection: {
|
|
|
|
/**
|
|
* @param {array} InjectedEventPluginOrder
|
|
* @public
|
|
*/
|
|
injectEventPluginOrder: EventPluginRegistry.injectEventPluginOrder,
|
|
|
|
/**
|
|
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
|
|
*/
|
|
injectEventPluginsByName: EventPluginRegistry.injectEventPluginsByName
|
|
|
|
},
|
|
|
|
/**
|
|
* Stores `listener` at `listenerBank[registrationName][key]`. Is idempotent.
|
|
*
|
|
* @param {object} inst The instance, which is the source of events.
|
|
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
|
* @param {function} listener The callback to store.
|
|
*/
|
|
putListener: function (inst, registrationName, listener) {
|
|
!(typeof listener === 'function') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected %s listener to be a function, instead got type %s', registrationName, typeof listener) : _prodInvariant('94', registrationName, typeof listener) : void 0;
|
|
|
|
var key = getDictionaryKey(inst);
|
|
var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
|
|
bankForRegistrationName[key] = listener;
|
|
|
|
var PluginModule = EventPluginRegistry.registrationNameModules[registrationName];
|
|
if (PluginModule && PluginModule.didPutListener) {
|
|
PluginModule.didPutListener(inst, registrationName, listener);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
getListener: function (inst, registrationName) {
|
|
// TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
|
|
// live here; needs to be moved to a better place soon
|
|
var bankForRegistrationName = listenerBank[registrationName];
|
|
if (shouldPreventMouseEvent(registrationName, inst._currentElement.type, inst._currentElement.props)) {
|
|
return null;
|
|
}
|
|
var key = getDictionaryKey(inst);
|
|
return bankForRegistrationName && bankForRegistrationName[key];
|
|
},
|
|
|
|
/**
|
|
* Deletes a listener from the registration bank.
|
|
*
|
|
* @param {object} inst The instance, which is the source of events.
|
|
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
|
*/
|
|
deleteListener: function (inst, registrationName) {
|
|
var PluginModule = EventPluginRegistry.registrationNameModules[registrationName];
|
|
if (PluginModule && PluginModule.willDeleteListener) {
|
|
PluginModule.willDeleteListener(inst, registrationName);
|
|
}
|
|
|
|
var bankForRegistrationName = listenerBank[registrationName];
|
|
// TODO: This should never be null -- when is it?
|
|
if (bankForRegistrationName) {
|
|
var key = getDictionaryKey(inst);
|
|
delete bankForRegistrationName[key];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Deletes all listeners for the DOM element with the supplied ID.
|
|
*
|
|
* @param {object} inst The instance, which is the source of events.
|
|
*/
|
|
deleteAllListeners: function (inst) {
|
|
var key = getDictionaryKey(inst);
|
|
for (var registrationName in listenerBank) {
|
|
if (!listenerBank.hasOwnProperty(registrationName)) {
|
|
continue;
|
|
}
|
|
|
|
if (!listenerBank[registrationName][key]) {
|
|
continue;
|
|
}
|
|
|
|
var PluginModule = EventPluginRegistry.registrationNameModules[registrationName];
|
|
if (PluginModule && PluginModule.willDeleteListener) {
|
|
PluginModule.willDeleteListener(inst, registrationName);
|
|
}
|
|
|
|
delete listenerBank[registrationName][key];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Allows registered plugins an opportunity to extract events from top-level
|
|
* native browser events.
|
|
*
|
|
* @return {*} An accumulation of synthetic events.
|
|
* @internal
|
|
*/
|
|
extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
|
|
var events;
|
|
var plugins = EventPluginRegistry.plugins;
|
|
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
|
|
*/
|
|
enqueueEvents: function (events) {
|
|
if (events) {
|
|
eventQueue = accumulateInto(eventQueue, events);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Dispatches all synthetic events on the event queue.
|
|
*
|
|
* @internal
|
|
*/
|
|
processEventQueue: function (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 (simulated) {
|
|
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseSimulated);
|
|
} else {
|
|
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
|
|
}
|
|
!!eventQueue ? process.env.NODE_ENV !== 'production' ? invariant(false, 'processEventQueue(): Additional events were enqueued while processing an event queue. Support for this has not yet been implemented.') : _prodInvariant('95') : void 0;
|
|
// This would be a good time to rethrow if any of the event handlers threw.
|
|
ReactErrorUtils.rethrowCaughtError();
|
|
},
|
|
|
|
/**
|
|
* These are needed for tests only. Do not use!
|
|
*/
|
|
__purge: function () {
|
|
listenerBank = {};
|
|
},
|
|
|
|
__getListenerBank: function () {
|
|
return listenerBank;
|
|
}
|
|
|
|
};
|
|
|
|
module.exports = EventPluginHub; |