UNPKG

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