/**1* Copyright 2013-2015, Facebook, Inc.2* All rights reserved.3*4* This source code is licensed under the BSD-style license found in the5* LICENSE file in the root directory of this source tree. An additional grant6* of patent rights can be found in the PATENTS file in the same directory.7*8* @providesModule ReactBrowserEventEmitter9* @typechecks static-only10*/1112'use strict';1314var EventConstants = require("./EventConstants");15var EventPluginHub = require("./EventPluginHub");16var EventPluginRegistry = require("./EventPluginRegistry");17var ReactEventEmitterMixin = require("./ReactEventEmitterMixin");18var ViewportMetrics = require("./ViewportMetrics");1920var assign = require("./Object.assign");21var isEventSupported = require("./isEventSupported");2223/**24* Summary of `ReactBrowserEventEmitter` event handling:25*26* - Top-level delegation is used to trap most native browser events. This27* may only occur in the main thread and is the responsibility of28* ReactEventListener, which is injected and can therefore support pluggable29* event sources. This is the only work that occurs in the main thread.30*31* - We normalize and de-duplicate events to account for browser quirks. This32* may be done in the worker thread.33*34* - Forward these native events (with the associated top-level type used to35* trap it) to `EventPluginHub`, which in turn will ask plugins if they want36* to extract any synthetic events.37*38* - The `EventPluginHub` will then process each event by annotating them with39* "dispatches", a sequence of listeners and IDs that care about that event.40*41* - The `EventPluginHub` then dispatches the events.42*43* Overview of React and the event system:44*45* +------------+ .46* | DOM | .47* +------------+ .48* | .49* v .50* +------------+ .51* | ReactEvent | .52* | Listener | .53* +------------+ . +-----------+54* | . +--------+|SimpleEvent|55* | . | |Plugin |56* +-----|------+ . v +-----------+57* | | | . +--------------+ +------------+58* | +-----------.--->|EventPluginHub| | Event |59* | | . | | +-----------+ | Propagators|60* | ReactEvent | . | | |TapEvent | |------------|61* | Emitter | . | |<---+|Plugin | |other plugin|62* | | . | | +-----------+ | utilities |63* | +-----------.--->| | +------------+64* | | | . +--------------+65* +-----|------+ . ^ +-----------+66* | . | |Enter/Leave|67* + . +-------+|Plugin |68* +-------------+ . +-----------+69* | application | .70* |-------------| .71* | | .72* | | .73* +-------------+ .74* .75* React Core . General Purpose Event Plugin System76*/7778var alreadyListeningTo = {};79var isMonitoringScrollValue = false;80var reactTopListenersCounter = 0;8182// For events like 'submit' which don't consistently bubble (which we trap at a83// lower node than `document`), binding at `document` would cause duplicate84// events so we don't include them here85var topEventMapping = {86topBlur: 'blur',87topChange: 'change',88topClick: 'click',89topCompositionEnd: 'compositionend',90topCompositionStart: 'compositionstart',91topCompositionUpdate: 'compositionupdate',92topContextMenu: 'contextmenu',93topCopy: 'copy',94topCut: 'cut',95topDoubleClick: 'dblclick',96topDrag: 'drag',97topDragEnd: 'dragend',98topDragEnter: 'dragenter',99topDragExit: 'dragexit',100topDragLeave: 'dragleave',101topDragOver: 'dragover',102topDragStart: 'dragstart',103topDrop: 'drop',104topFocus: 'focus',105topInput: 'input',106topKeyDown: 'keydown',107topKeyPress: 'keypress',108topKeyUp: 'keyup',109topMouseDown: 'mousedown',110topMouseMove: 'mousemove',111topMouseOut: 'mouseout',112topMouseOver: 'mouseover',113topMouseUp: 'mouseup',114topPaste: 'paste',115topScroll: 'scroll',116topSelectionChange: 'selectionchange',117topTextInput: 'textInput',118topTouchCancel: 'touchcancel',119topTouchEnd: 'touchend',120topTouchMove: 'touchmove',121topTouchStart: 'touchstart',122topWheel: 'wheel'123};124125/**126* To ensure no conflicts with other potential React instances on the page127*/128var topListenersIDKey = '_reactListenersID' + String(Math.random()).slice(2);129130function getListeningForDocument(mountAt) {131// In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty`132// directly.133if (!Object.prototype.hasOwnProperty.call(mountAt, topListenersIDKey)) {134mountAt[topListenersIDKey] = reactTopListenersCounter++;135alreadyListeningTo[mountAt[topListenersIDKey]] = {};136}137return alreadyListeningTo[mountAt[topListenersIDKey]];138}139140/**141* `ReactBrowserEventEmitter` is used to attach top-level event listeners. For142* example:143*144* ReactBrowserEventEmitter.putListener('myID', 'onClick', myFunction);145*146* This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.147*148* @internal149*/150var ReactBrowserEventEmitter = assign({}, ReactEventEmitterMixin, {151152/**153* Injectable event backend154*/155ReactEventListener: null,156157injection: {158/**159* @param {object} ReactEventListener160*/161injectReactEventListener: function(ReactEventListener) {162ReactEventListener.setHandleTopLevel(163ReactBrowserEventEmitter.handleTopLevel164);165ReactBrowserEventEmitter.ReactEventListener = ReactEventListener;166}167},168169/**170* Sets whether or not any created callbacks should be enabled.171*172* @param {boolean} enabled True if callbacks should be enabled.173*/174setEnabled: function(enabled) {175if (ReactBrowserEventEmitter.ReactEventListener) {176ReactBrowserEventEmitter.ReactEventListener.setEnabled(enabled);177}178},179180/**181* @return {boolean} True if callbacks are enabled.182*/183isEnabled: function() {184return !!(185(ReactBrowserEventEmitter.ReactEventListener && ReactBrowserEventEmitter.ReactEventListener.isEnabled())186);187},188189/**190* We listen for bubbled touch events on the document object.191*192* Firefox v8.01 (and possibly others) exhibited strange behavior when193* mounting `onmousemove` events at some node that was not the document194* element. The symptoms were that if your mouse is not moving over something195* contained within that mount point (for example on the background) the196* top-level listeners for `onmousemove` won't be called. However, if you197* register the `mousemove` on the document object, then it will of course198* catch all `mousemove`s. This along with iOS quirks, justifies restricting199* top-level listeners to the document object only, at least for these200* movement types of events and possibly all events.201*202* @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html203*204* Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but205* they bubble to document.206*207* @param {string} registrationName Name of listener (e.g. `onClick`).208* @param {object} contentDocumentHandle Document which owns the container209*/210listenTo: function(registrationName, contentDocumentHandle) {211var mountAt = contentDocumentHandle;212var isListening = getListeningForDocument(mountAt);213var dependencies = EventPluginRegistry.214registrationNameDependencies[registrationName];215216var topLevelTypes = EventConstants.topLevelTypes;217for (var i = 0, l = dependencies.length; i < l; i++) {218var dependency = dependencies[i];219if (!(220(isListening.hasOwnProperty(dependency) && isListening[dependency])221)) {222if (dependency === topLevelTypes.topWheel) {223if (isEventSupported('wheel')) {224ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(225topLevelTypes.topWheel,226'wheel',227mountAt228);229} else if (isEventSupported('mousewheel')) {230ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(231topLevelTypes.topWheel,232'mousewheel',233mountAt234);235} else {236// Firefox needs to capture a different mouse scroll event.237// @see http://www.quirksmode.org/dom/events/tests/scroll.html238ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(239topLevelTypes.topWheel,240'DOMMouseScroll',241mountAt242);243}244} else if (dependency === topLevelTypes.topScroll) {245246if (isEventSupported('scroll', true)) {247ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(248topLevelTypes.topScroll,249'scroll',250mountAt251);252} else {253ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(254topLevelTypes.topScroll,255'scroll',256ReactBrowserEventEmitter.ReactEventListener.WINDOW_HANDLE257);258}259} else if (dependency === topLevelTypes.topFocus ||260dependency === topLevelTypes.topBlur) {261262if (isEventSupported('focus', true)) {263ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(264topLevelTypes.topFocus,265'focus',266mountAt267);268ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(269topLevelTypes.topBlur,270'blur',271mountAt272);273} else if (isEventSupported('focusin')) {274// IE has `focusin` and `focusout` events which bubble.275// @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html276ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(277topLevelTypes.topFocus,278'focusin',279mountAt280);281ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(282topLevelTypes.topBlur,283'focusout',284mountAt285);286}287288// to make sure blur and focus event listeners are only attached once289isListening[topLevelTypes.topBlur] = true;290isListening[topLevelTypes.topFocus] = true;291} else if (topEventMapping.hasOwnProperty(dependency)) {292ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(293dependency,294topEventMapping[dependency],295mountAt296);297}298299isListening[dependency] = true;300}301}302},303304trapBubbledEvent: function(topLevelType, handlerBaseName, handle) {305return ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(306topLevelType,307handlerBaseName,308handle309);310},311312trapCapturedEvent: function(topLevelType, handlerBaseName, handle) {313return ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(314topLevelType,315handlerBaseName,316handle317);318},319320/**321* Listens to window scroll and resize events. We cache scroll values so that322* application code can access them without triggering reflows.323*324* NOTE: Scroll events do not bubble.325*326* @see http://www.quirksmode.org/dom/events/scroll.html327*/328ensureScrollValueMonitoring: function() {329if (!isMonitoringScrollValue) {330var refresh = ViewportMetrics.refreshScrollValues;331ReactBrowserEventEmitter.ReactEventListener.monitorScrollValue(refresh);332isMonitoringScrollValue = true;333}334},335336eventNameDispatchConfigs: EventPluginHub.eventNameDispatchConfigs,337338registrationNameModules: EventPluginHub.registrationNameModules,339340putListener: EventPluginHub.putListener,341342getListener: EventPluginHub.getListener,343344deleteListener: EventPluginHub.deleteListener,345346deleteAllListeners: EventPluginHub.deleteAllListeners347348});349350module.exports = ReactBrowserEventEmitter;351352353