UNPKG

9.18 kBJavaScriptView Raw
1/**
2 * Copyright 2013-present, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
8 *
9 */
10
11'use strict';
12
13var _assign = require('object-assign');
14
15var PooledClass = require('./PooledClass');
16
17var emptyFunction = require('fbjs/lib/emptyFunction');
18var warning = require('fbjs/lib/warning');
19
20var didWarnForAddedNewProperty = false;
21var isProxySupported = typeof Proxy === 'function';
22
23var shouldBeReleasedProperties = ['dispatchConfig', '_targetInst', 'nativeEvent', 'isDefaultPrevented', 'isPropagationStopped', '_dispatchListeners', '_dispatchInstances'];
24
25/**
26 * @interface Event
27 * @see http://www.w3.org/TR/DOM-Level-3-Events/
28 */
29var EventInterface = {
30 type: null,
31 target: null,
32 // currentTarget is set when dispatching; no use in copying it here
33 currentTarget: emptyFunction.thatReturnsNull,
34 eventPhase: null,
35 bubbles: null,
36 cancelable: null,
37 timeStamp: function (event) {
38 return event.timeStamp || Date.now();
39 },
40 defaultPrevented: null,
41 isTrusted: null
42};
43
44/**
45 * Synthetic events are dispatched by event plugins, typically in response to a
46 * top-level event delegation handler.
47 *
48 * These systems should generally use pooling to reduce the frequency of garbage
49 * collection. The system should check `isPersistent` to determine whether the
50 * event should be released into the pool after being dispatched. Users that
51 * need a persisted event should invoke `persist`.
52 *
53 * Synthetic events (and subclasses) implement the DOM Level 3 Events API by
54 * normalizing browser quirks. Subclasses do not necessarily have to implement a
55 * DOM interface; custom application-specific events can also subclass this.
56 *
57 * @param {object} dispatchConfig Configuration used to dispatch this event.
58 * @param {*} targetInst Marker identifying the event target.
59 * @param {object} nativeEvent Native browser event.
60 * @param {DOMEventTarget} nativeEventTarget Target node.
61 */
62function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
63 if (process.env.NODE_ENV !== 'production') {
64 // these have a getter/setter for warnings
65 delete this.nativeEvent;
66 delete this.preventDefault;
67 delete this.stopPropagation;
68 }
69
70 this.dispatchConfig = dispatchConfig;
71 this._targetInst = targetInst;
72 this.nativeEvent = nativeEvent;
73
74 var Interface = this.constructor.Interface;
75 for (var propName in Interface) {
76 if (!Interface.hasOwnProperty(propName)) {
77 continue;
78 }
79 if (process.env.NODE_ENV !== 'production') {
80 delete this[propName]; // this has a getter/setter for warnings
81 }
82 var normalize = Interface[propName];
83 if (normalize) {
84 this[propName] = normalize(nativeEvent);
85 } else {
86 if (propName === 'target') {
87 this.target = nativeEventTarget;
88 } else {
89 this[propName] = nativeEvent[propName];
90 }
91 }
92 }
93
94 var defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false;
95 if (defaultPrevented) {
96 this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
97 } else {
98 this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
99 }
100 this.isPropagationStopped = emptyFunction.thatReturnsFalse;
101 return this;
102}
103
104_assign(SyntheticEvent.prototype, {
105 preventDefault: function () {
106 this.defaultPrevented = true;
107 var event = this.nativeEvent;
108 if (!event) {
109 return;
110 }
111
112 if (event.preventDefault) {
113 event.preventDefault();
114 // eslint-disable-next-line valid-typeof
115 } else if (typeof event.returnValue !== 'unknown') {
116 event.returnValue = false;
117 }
118 this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
119 },
120
121 stopPropagation: function () {
122 var event = this.nativeEvent;
123 if (!event) {
124 return;
125 }
126
127 if (event.stopPropagation) {
128 event.stopPropagation();
129 // eslint-disable-next-line valid-typeof
130 } else if (typeof event.cancelBubble !== 'unknown') {
131 // The ChangeEventPlugin registers a "propertychange" event for
132 // IE. This event does not support bubbling or cancelling, and
133 // any references to cancelBubble throw "Member not found". A
134 // typeof check of "unknown" circumvents this issue (and is also
135 // IE specific).
136 event.cancelBubble = true;
137 }
138
139 this.isPropagationStopped = emptyFunction.thatReturnsTrue;
140 },
141
142 /**
143 * We release all dispatched `SyntheticEvent`s after each event loop, adding
144 * them back into the pool. This allows a way to hold onto a reference that
145 * won't be added back into the pool.
146 */
147 persist: function () {
148 this.isPersistent = emptyFunction.thatReturnsTrue;
149 },
150
151 /**
152 * Checks if this event should be released back into the pool.
153 *
154 * @return {boolean} True if this should not be released, false otherwise.
155 */
156 isPersistent: emptyFunction.thatReturnsFalse,
157
158 /**
159 * `PooledClass` looks for `destructor` on each instance it releases.
160 */
161 destructor: function () {
162 var Interface = this.constructor.Interface;
163 for (var propName in Interface) {
164 if (process.env.NODE_ENV !== 'production') {
165 Object.defineProperty(this, propName, getPooledWarningPropertyDefinition(propName, Interface[propName]));
166 } else {
167 this[propName] = null;
168 }
169 }
170 for (var i = 0; i < shouldBeReleasedProperties.length; i++) {
171 this[shouldBeReleasedProperties[i]] = null;
172 }
173 if (process.env.NODE_ENV !== 'production') {
174 Object.defineProperty(this, 'nativeEvent', getPooledWarningPropertyDefinition('nativeEvent', null));
175 Object.defineProperty(this, 'preventDefault', getPooledWarningPropertyDefinition('preventDefault', emptyFunction));
176 Object.defineProperty(this, 'stopPropagation', getPooledWarningPropertyDefinition('stopPropagation', emptyFunction));
177 }
178 }
179});
180
181SyntheticEvent.Interface = EventInterface;
182
183if (process.env.NODE_ENV !== 'production') {
184 if (isProxySupported) {
185 /*eslint-disable no-func-assign */
186 SyntheticEvent = new Proxy(SyntheticEvent, {
187 construct: function (target, args) {
188 return this.apply(target, Object.create(target.prototype), args);
189 },
190 apply: function (constructor, that, args) {
191 return new Proxy(constructor.apply(that, args), {
192 set: function (target, prop, value) {
193 if (prop !== 'isPersistent' && !target.constructor.Interface.hasOwnProperty(prop) && shouldBeReleasedProperties.indexOf(prop) === -1) {
194 process.env.NODE_ENV !== 'production' ? 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.') : void 0;
195 didWarnForAddedNewProperty = true;
196 }
197 target[prop] = value;
198 return true;
199 }
200 });
201 }
202 });
203 /*eslint-enable no-func-assign */
204 }
205}
206/**
207 * Helper to reduce boilerplate when creating subclasses.
208 *
209 * @param {function} Class
210 * @param {?object} Interface
211 */
212SyntheticEvent.augmentClass = function (Class, Interface) {
213 var Super = this;
214
215 var E = function () {};
216 E.prototype = Super.prototype;
217 var prototype = new E();
218
219 _assign(prototype, Class.prototype);
220 Class.prototype = prototype;
221 Class.prototype.constructor = Class;
222
223 Class.Interface = _assign({}, Super.Interface, Interface);
224 Class.augmentClass = Super.augmentClass;
225
226 PooledClass.addPoolingTo(Class, PooledClass.fourArgumentPooler);
227};
228
229PooledClass.addPoolingTo(SyntheticEvent, PooledClass.fourArgumentPooler);
230
231module.exports = SyntheticEvent;
232
233/**
234 * Helper to nullify syntheticEvent instance properties when destructing
235 *
236 * @param {object} SyntheticEvent
237 * @param {String} propName
238 * @return {object} defineProperty object
239 */
240function getPooledWarningPropertyDefinition(propName, getVal) {
241 var isFunction = typeof getVal === 'function';
242 return {
243 configurable: true,
244 set: set,
245 get: get
246 };
247
248 function set(val) {
249 var action = isFunction ? 'setting the method' : 'setting the property';
250 warn(action, 'This is effectively a no-op');
251 return val;
252 }
253
254 function get() {
255 var action = isFunction ? 'accessing the method' : 'accessing the property';
256 var result = isFunction ? 'This is a no-op function' : 'This is set to null';
257 warn(action, result);
258 return getVal;
259 }
260
261 function warn(action, result) {
262 var warningCondition = false;
263 process.env.NODE_ENV !== 'production' ? 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) : void 0;
264 }
265}
\No newline at end of file