You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
352 lines
12 KiB
352 lines
12 KiB
10 years ago
|
/**
|
||
|
* Copyright 2013-2015, 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 ReactBrowserEventEmitter
|
||
|
* @typechecks static-only
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var EventConstants = require("./EventConstants");
|
||
|
var EventPluginHub = require("./EventPluginHub");
|
||
|
var EventPluginRegistry = require("./EventPluginRegistry");
|
||
|
var ReactEventEmitterMixin = require("./ReactEventEmitterMixin");
|
||
|
var ViewportMetrics = require("./ViewportMetrics");
|
||
|
|
||
|
var assign = require("./Object.assign");
|
||
|
var isEventSupported = require("./isEventSupported");
|
||
|
|
||
|
/**
|
||
|
* Summary of `ReactBrowserEventEmitter` event handling:
|
||
|
*
|
||
|
* - Top-level delegation is used to trap most native browser events. This
|
||
|
* may only occur in the main thread and is the responsibility of
|
||
|
* ReactEventListener, which is injected and can therefore support pluggable
|
||
|
* event sources. This is the only work that occurs in the main thread.
|
||
|
*
|
||
|
* - We normalize and de-duplicate events to account for browser quirks. This
|
||
|
* may be done in the worker thread.
|
||
|
*
|
||
|
* - Forward these native events (with the associated top-level type used to
|
||
|
* trap it) to `EventPluginHub`, which in turn will ask plugins if they want
|
||
|
* to extract any synthetic events.
|
||
|
*
|
||
|
* - The `EventPluginHub` will then process each event by annotating them with
|
||
|
* "dispatches", a sequence of listeners and IDs that care about that event.
|
||
|
*
|
||
|
* - The `EventPluginHub` then dispatches the events.
|
||
|
*
|
||
|
* Overview of React and the event system:
|
||
|
*
|
||
|
* +------------+ .
|
||
|
* | DOM | .
|
||
|
* +------------+ .
|
||
|
* | .
|
||
|
* v .
|
||
|
* +------------+ .
|
||
|
* | ReactEvent | .
|
||
|
* | Listener | .
|
||
|
* +------------+ . +-----------+
|
||
|
* | . +--------+|SimpleEvent|
|
||
|
* | . | |Plugin |
|
||
|
* +-----|------+ . v +-----------+
|
||
|
* | | | . +--------------+ +------------+
|
||
|
* | +-----------.--->|EventPluginHub| | Event |
|
||
|
* | | . | | +-----------+ | Propagators|
|
||
|
* | ReactEvent | . | | |TapEvent | |------------|
|
||
|
* | Emitter | . | |<---+|Plugin | |other plugin|
|
||
|
* | | . | | +-----------+ | utilities |
|
||
|
* | +-----------.--->| | +------------+
|
||
|
* | | | . +--------------+
|
||
|
* +-----|------+ . ^ +-----------+
|
||
|
* | . | |Enter/Leave|
|
||
|
* + . +-------+|Plugin |
|
||
|
* +-------------+ . +-----------+
|
||
|
* | application | .
|
||
|
* |-------------| .
|
||
|
* | | .
|
||
|
* | | .
|
||
|
* +-------------+ .
|
||
|
* .
|
||
|
* React Core . General Purpose Event Plugin System
|
||
|
*/
|
||
|
|
||
|
var alreadyListeningTo = {};
|
||
|
var isMonitoringScrollValue = false;
|
||
|
var reactTopListenersCounter = 0;
|
||
|
|
||
|
// For events like 'submit' which don't consistently bubble (which we trap at a
|
||
|
// lower node than `document`), binding at `document` would cause duplicate
|
||
|
// events so we don't include them here
|
||
|
var topEventMapping = {
|
||
|
topBlur: 'blur',
|
||
|
topChange: 'change',
|
||
|
topClick: 'click',
|
||
|
topCompositionEnd: 'compositionend',
|
||
|
topCompositionStart: 'compositionstart',
|
||
|
topCompositionUpdate: 'compositionupdate',
|
||
|
topContextMenu: 'contextmenu',
|
||
|
topCopy: 'copy',
|
||
|
topCut: 'cut',
|
||
|
topDoubleClick: 'dblclick',
|
||
|
topDrag: 'drag',
|
||
|
topDragEnd: 'dragend',
|
||
|
topDragEnter: 'dragenter',
|
||
|
topDragExit: 'dragexit',
|
||
|
topDragLeave: 'dragleave',
|
||
|
topDragOver: 'dragover',
|
||
|
topDragStart: 'dragstart',
|
||
|
topDrop: 'drop',
|
||
|
topFocus: 'focus',
|
||
|
topInput: 'input',
|
||
|
topKeyDown: 'keydown',
|
||
|
topKeyPress: 'keypress',
|
||
|
topKeyUp: 'keyup',
|
||
|
topMouseDown: 'mousedown',
|
||
|
topMouseMove: 'mousemove',
|
||
|
topMouseOut: 'mouseout',
|
||
|
topMouseOver: 'mouseover',
|
||
|
topMouseUp: 'mouseup',
|
||
|
topPaste: 'paste',
|
||
|
topScroll: 'scroll',
|
||
|
topSelectionChange: 'selectionchange',
|
||
|
topTextInput: 'textInput',
|
||
|
topTouchCancel: 'touchcancel',
|
||
|
topTouchEnd: 'touchend',
|
||
|
topTouchMove: 'touchmove',
|
||
|
topTouchStart: 'touchstart',
|
||
|
topWheel: 'wheel'
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* To ensure no conflicts with other potential React instances on the page
|
||
|
*/
|
||
|
var topListenersIDKey = '_reactListenersID' + String(Math.random()).slice(2);
|
||
|
|
||
|
function getListeningForDocument(mountAt) {
|
||
|
// In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty`
|
||
|
// directly.
|
||
|
if (!Object.prototype.hasOwnProperty.call(mountAt, topListenersIDKey)) {
|
||
|
mountAt[topListenersIDKey] = reactTopListenersCounter++;
|
||
|
alreadyListeningTo[mountAt[topListenersIDKey]] = {};
|
||
|
}
|
||
|
return alreadyListeningTo[mountAt[topListenersIDKey]];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* `ReactBrowserEventEmitter` is used to attach top-level event listeners. For
|
||
|
* example:
|
||
|
*
|
||
|
* ReactBrowserEventEmitter.putListener('myID', 'onClick', myFunction);
|
||
|
*
|
||
|
* This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
|
||
|
*
|
||
|
* @internal
|
||
|
*/
|
||
|
var ReactBrowserEventEmitter = assign({}, ReactEventEmitterMixin, {
|
||
|
|
||
|
/**
|
||
|
* Injectable event backend
|
||
|
*/
|
||
|
ReactEventListener: null,
|
||
|
|
||
|
injection: {
|
||
|
/**
|
||
|
* @param {object} ReactEventListener
|
||
|
*/
|
||
|
injectReactEventListener: function(ReactEventListener) {
|
||
|
ReactEventListener.setHandleTopLevel(
|
||
|
ReactBrowserEventEmitter.handleTopLevel
|
||
|
);
|
||
|
ReactBrowserEventEmitter.ReactEventListener = ReactEventListener;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets whether or not any created callbacks should be enabled.
|
||
|
*
|
||
|
* @param {boolean} enabled True if callbacks should be enabled.
|
||
|
*/
|
||
|
setEnabled: function(enabled) {
|
||
|
if (ReactBrowserEventEmitter.ReactEventListener) {
|
||
|
ReactBrowserEventEmitter.ReactEventListener.setEnabled(enabled);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @return {boolean} True if callbacks are enabled.
|
||
|
*/
|
||
|
isEnabled: function() {
|
||
|
return !!(
|
||
|
(ReactBrowserEventEmitter.ReactEventListener && ReactBrowserEventEmitter.ReactEventListener.isEnabled())
|
||
|
);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* We listen for bubbled touch events on the document object.
|
||
|
*
|
||
|
* Firefox v8.01 (and possibly others) exhibited strange behavior when
|
||
|
* mounting `onmousemove` events at some node that was not the document
|
||
|
* element. The symptoms were that if your mouse is not moving over something
|
||
|
* contained within that mount point (for example on the background) the
|
||
|
* top-level listeners for `onmousemove` won't be called. However, if you
|
||
|
* register the `mousemove` on the document object, then it will of course
|
||
|
* catch all `mousemove`s. This along with iOS quirks, justifies restricting
|
||
|
* top-level listeners to the document object only, at least for these
|
||
|
* movement types of events and possibly all events.
|
||
|
*
|
||
|
* @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
|
||
|
*
|
||
|
* Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
|
||
|
* they bubble to document.
|
||
|
*
|
||
|
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||
|
* @param {object} contentDocumentHandle Document which owns the container
|
||
|
*/
|
||
|
listenTo: function(registrationName, contentDocumentHandle) {
|
||
|
var mountAt = contentDocumentHandle;
|
||
|
var isListening = getListeningForDocument(mountAt);
|
||
|
var dependencies = EventPluginRegistry.
|
||
|
registrationNameDependencies[registrationName];
|
||
|
|
||
|
var topLevelTypes = EventConstants.topLevelTypes;
|
||
|
for (var i = 0, l = dependencies.length; i < l; i++) {
|
||
|
var dependency = dependencies[i];
|
||
|
if (!(
|
||
|
(isListening.hasOwnProperty(dependency) && isListening[dependency])
|
||
|
)) {
|
||
|
if (dependency === topLevelTypes.topWheel) {
|
||
|
if (isEventSupported('wheel')) {
|
||
|
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||
|
topLevelTypes.topWheel,
|
||
|
'wheel',
|
||
|
mountAt
|
||
|
);
|
||
|
} else if (isEventSupported('mousewheel')) {
|
||
|
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||
|
topLevelTypes.topWheel,
|
||
|
'mousewheel',
|
||
|
mountAt
|
||
|
);
|
||
|
} else {
|
||
|
// Firefox needs to capture a different mouse scroll event.
|
||
|
// @see http://www.quirksmode.org/dom/events/tests/scroll.html
|
||
|
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||
|
topLevelTypes.topWheel,
|
||
|
'DOMMouseScroll',
|
||
|
mountAt
|
||
|
);
|
||
|
}
|
||
|
} else if (dependency === topLevelTypes.topScroll) {
|
||
|
|
||
|
if (isEventSupported('scroll', true)) {
|
||
|
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
|
||
|
topLevelTypes.topScroll,
|
||
|
'scroll',
|
||
|
mountAt
|
||
|
);
|
||
|
} else {
|
||
|
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||
|
topLevelTypes.topScroll,
|
||
|
'scroll',
|
||
|
ReactBrowserEventEmitter.ReactEventListener.WINDOW_HANDLE
|
||
|
);
|
||
|
}
|
||
|
} else if (dependency === topLevelTypes.topFocus ||
|
||
|
dependency === topLevelTypes.topBlur) {
|
||
|
|
||
|
if (isEventSupported('focus', true)) {
|
||
|
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
|
||
|
topLevelTypes.topFocus,
|
||
|
'focus',
|
||
|
mountAt
|
||
|
);
|
||
|
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
|
||
|
topLevelTypes.topBlur,
|
||
|
'blur',
|
||
|
mountAt
|
||
|
);
|
||
|
} else if (isEventSupported('focusin')) {
|
||
|
// IE has `focusin` and `focusout` events which bubble.
|
||
|
// @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
|
||
|
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||
|
topLevelTypes.topFocus,
|
||
|
'focusin',
|
||
|
mountAt
|
||
|
);
|
||
|
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||
|
topLevelTypes.topBlur,
|
||
|
'focusout',
|
||
|
mountAt
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// to make sure blur and focus event listeners are only attached once
|
||
|
isListening[topLevelTypes.topBlur] = true;
|
||
|
isListening[topLevelTypes.topFocus] = true;
|
||
|
} else if (topEventMapping.hasOwnProperty(dependency)) {
|
||
|
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||
|
dependency,
|
||
|
topEventMapping[dependency],
|
||
|
mountAt
|
||
|
);
|
||
|
}
|
||
|
|
||
|
isListening[dependency] = true;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
trapBubbledEvent: function(topLevelType, handlerBaseName, handle) {
|
||
|
return ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
|
||
|
topLevelType,
|
||
|
handlerBaseName,
|
||
|
handle
|
||
|
);
|
||
|
},
|
||
|
|
||
|
trapCapturedEvent: function(topLevelType, handlerBaseName, handle) {
|
||
|
return ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
|
||
|
topLevelType,
|
||
|
handlerBaseName,
|
||
|
handle
|
||
|
);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Listens to window scroll and resize events. We cache scroll values so that
|
||
|
* application code can access them without triggering reflows.
|
||
|
*
|
||
|
* NOTE: Scroll events do not bubble.
|
||
|
*
|
||
|
* @see http://www.quirksmode.org/dom/events/scroll.html
|
||
|
*/
|
||
|
ensureScrollValueMonitoring: function() {
|
||
|
if (!isMonitoringScrollValue) {
|
||
|
var refresh = ViewportMetrics.refreshScrollValues;
|
||
|
ReactBrowserEventEmitter.ReactEventListener.monitorScrollValue(refresh);
|
||
|
isMonitoringScrollValue = true;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
eventNameDispatchConfigs: EventPluginHub.eventNameDispatchConfigs,
|
||
|
|
||
|
registrationNameModules: EventPluginHub.registrationNameModules,
|
||
|
|
||
|
putListener: EventPluginHub.putListener,
|
||
|
|
||
|
getListener: EventPluginHub.getListener,
|
||
|
|
||
|
deleteListener: EventPluginHub.deleteListener,
|
||
|
|
||
|
deleteAllListeners: EventPluginHub.deleteAllListeners
|
||
|
|
||
|
});
|
||
|
|
||
|
module.exports = ReactBrowserEventEmitter;
|