mirror of
https://github.com/zebrajr/react.git
synced 2025-12-07 12:20:38 +01:00
* Inline fbjs/lib/invariant * Inline fbjs/lib/warning * Remove remaining usage of fbjs in packages/*.js * Fix lint * Remove fbjs from dependencies * Protect against accidental fbjs imports * Fix broken test mocks * Allow transitive deps on fbjs/ for UMD bundles * Remove fbjs from release script
372 lines
10 KiB
JavaScript
372 lines
10 KiB
JavaScript
/**
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
/* eslint valid-typeof: 0 */
|
|
|
|
import invariant from 'shared/invariant';
|
|
import warning from 'shared/warning';
|
|
|
|
let didWarnForAddedNewProperty = false;
|
|
const EVENT_POOL_SIZE = 10;
|
|
|
|
const shouldBeReleasedProperties = [
|
|
'dispatchConfig',
|
|
'_targetInst',
|
|
'nativeEvent',
|
|
'isDefaultPrevented',
|
|
'isPropagationStopped',
|
|
'_dispatchListeners',
|
|
'_dispatchInstances',
|
|
];
|
|
|
|
/**
|
|
* @interface Event
|
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/
|
|
*/
|
|
const EventInterface = {
|
|
type: null,
|
|
target: null,
|
|
// currentTarget is set when dispatching; no use in copying it here
|
|
currentTarget: function() {
|
|
return null;
|
|
},
|
|
eventPhase: null,
|
|
bubbles: null,
|
|
cancelable: null,
|
|
timeStamp: function(event) {
|
|
return event.timeStamp || Date.now();
|
|
},
|
|
defaultPrevented: null,
|
|
isTrusted: null,
|
|
};
|
|
|
|
function functionThatReturnsTrue() {
|
|
return true;
|
|
}
|
|
|
|
function functionThatReturnsFalse() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Synthetic events are dispatched by event plugins, typically in response to a
|
|
* top-level event delegation handler.
|
|
*
|
|
* These systems should generally use pooling to reduce the frequency of garbage
|
|
* collection. The system should check `isPersistent` to determine whether the
|
|
* event should be released into the pool after being dispatched. Users that
|
|
* need a persisted event should invoke `persist`.
|
|
*
|
|
* Synthetic events (and subclasses) implement the DOM Level 3 Events API by
|
|
* normalizing browser quirks. Subclasses do not necessarily have to implement a
|
|
* DOM interface; custom application-specific events can also subclass this.
|
|
*
|
|
* @param {object} dispatchConfig Configuration used to dispatch this event.
|
|
* @param {*} targetInst Marker identifying the event target.
|
|
* @param {object} nativeEvent Native browser event.
|
|
* @param {DOMEventTarget} nativeEventTarget Target node.
|
|
*/
|
|
function SyntheticEvent(
|
|
dispatchConfig,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeEventTarget,
|
|
) {
|
|
if (__DEV__) {
|
|
// these have a getter/setter for warnings
|
|
delete this.nativeEvent;
|
|
delete this.preventDefault;
|
|
delete this.stopPropagation;
|
|
}
|
|
|
|
this.dispatchConfig = dispatchConfig;
|
|
this._targetInst = targetInst;
|
|
this.nativeEvent = nativeEvent;
|
|
|
|
const Interface = this.constructor.Interface;
|
|
for (const propName in Interface) {
|
|
if (!Interface.hasOwnProperty(propName)) {
|
|
continue;
|
|
}
|
|
if (__DEV__) {
|
|
delete this[propName]; // this has a getter/setter for warnings
|
|
}
|
|
const normalize = Interface[propName];
|
|
if (normalize) {
|
|
this[propName] = normalize(nativeEvent);
|
|
} else {
|
|
if (propName === 'target') {
|
|
this.target = nativeEventTarget;
|
|
} else {
|
|
this[propName] = nativeEvent[propName];
|
|
}
|
|
}
|
|
}
|
|
|
|
const defaultPrevented =
|
|
nativeEvent.defaultPrevented != null
|
|
? nativeEvent.defaultPrevented
|
|
: nativeEvent.returnValue === false;
|
|
if (defaultPrevented) {
|
|
this.isDefaultPrevented = functionThatReturnsTrue;
|
|
} else {
|
|
this.isDefaultPrevented = functionThatReturnsFalse;
|
|
}
|
|
this.isPropagationStopped = functionThatReturnsFalse;
|
|
return this;
|
|
}
|
|
|
|
Object.assign(SyntheticEvent.prototype, {
|
|
preventDefault: function() {
|
|
this.defaultPrevented = true;
|
|
const event = this.nativeEvent;
|
|
if (!event) {
|
|
return;
|
|
}
|
|
|
|
if (event.preventDefault) {
|
|
event.preventDefault();
|
|
} else if (typeof event.returnValue !== 'unknown') {
|
|
event.returnValue = false;
|
|
}
|
|
this.isDefaultPrevented = functionThatReturnsTrue;
|
|
},
|
|
|
|
stopPropagation: function() {
|
|
const event = this.nativeEvent;
|
|
if (!event) {
|
|
return;
|
|
}
|
|
|
|
if (event.stopPropagation) {
|
|
event.stopPropagation();
|
|
} else if (typeof event.cancelBubble !== 'unknown') {
|
|
// The ChangeEventPlugin registers a "propertychange" event for
|
|
// IE. This event does not support bubbling or cancelling, and
|
|
// any references to cancelBubble throw "Member not found". A
|
|
// typeof check of "unknown" circumvents this issue (and is also
|
|
// IE specific).
|
|
event.cancelBubble = true;
|
|
}
|
|
|
|
this.isPropagationStopped = functionThatReturnsTrue;
|
|
},
|
|
|
|
/**
|
|
* We release all dispatched `SyntheticEvent`s after each event loop, adding
|
|
* them back into the pool. This allows a way to hold onto a reference that
|
|
* won't be added back into the pool.
|
|
*/
|
|
persist: function() {
|
|
this.isPersistent = functionThatReturnsTrue;
|
|
},
|
|
|
|
/**
|
|
* Checks if this event should be released back into the pool.
|
|
*
|
|
* @return {boolean} True if this should not be released, false otherwise.
|
|
*/
|
|
isPersistent: functionThatReturnsFalse,
|
|
|
|
/**
|
|
* `PooledClass` looks for `destructor` on each instance it releases.
|
|
*/
|
|
destructor: function() {
|
|
const Interface = this.constructor.Interface;
|
|
for (const propName in Interface) {
|
|
if (__DEV__) {
|
|
Object.defineProperty(
|
|
this,
|
|
propName,
|
|
getPooledWarningPropertyDefinition(propName, Interface[propName]),
|
|
);
|
|
} else {
|
|
this[propName] = null;
|
|
}
|
|
}
|
|
for (let i = 0; i < shouldBeReleasedProperties.length; i++) {
|
|
this[shouldBeReleasedProperties[i]] = null;
|
|
}
|
|
if (__DEV__) {
|
|
Object.defineProperty(
|
|
this,
|
|
'nativeEvent',
|
|
getPooledWarningPropertyDefinition('nativeEvent', null),
|
|
);
|
|
Object.defineProperty(
|
|
this,
|
|
'preventDefault',
|
|
getPooledWarningPropertyDefinition('preventDefault', () => {}),
|
|
);
|
|
Object.defineProperty(
|
|
this,
|
|
'stopPropagation',
|
|
getPooledWarningPropertyDefinition('stopPropagation', () => {}),
|
|
);
|
|
}
|
|
},
|
|
});
|
|
|
|
SyntheticEvent.Interface = EventInterface;
|
|
|
|
/**
|
|
* Helper to reduce boilerplate when creating subclasses.
|
|
*/
|
|
SyntheticEvent.extend = function(Interface) {
|
|
const Super = this;
|
|
|
|
const E = function() {};
|
|
E.prototype = Super.prototype;
|
|
const prototype = new E();
|
|
|
|
function Class() {
|
|
return Super.apply(this, arguments);
|
|
}
|
|
Object.assign(prototype, Class.prototype);
|
|
Class.prototype = prototype;
|
|
Class.prototype.constructor = Class;
|
|
|
|
Class.Interface = Object.assign({}, Super.Interface, Interface);
|
|
Class.extend = Super.extend;
|
|
addEventPoolingTo(Class);
|
|
|
|
return Class;
|
|
};
|
|
|
|
/** Proxying after everything set on SyntheticEvent
|
|
* to resolve Proxy issue on some WebKit browsers
|
|
* in which some Event properties are set to undefined (GH#10010)
|
|
*/
|
|
if (__DEV__) {
|
|
const isProxySupported =
|
|
typeof Proxy === 'function' &&
|
|
// https://github.com/facebook/react/issues/12011
|
|
!Object.isSealed(new Proxy({}, {}));
|
|
|
|
if (isProxySupported) {
|
|
/*eslint-disable no-func-assign */
|
|
SyntheticEvent = new Proxy(SyntheticEvent, {
|
|
construct: function(target, args) {
|
|
return this.apply(target, Object.create(target.prototype), args);
|
|
},
|
|
apply: function(constructor, that, args) {
|
|
return new Proxy(constructor.apply(that, args), {
|
|
set: function(target, prop, value) {
|
|
if (
|
|
prop !== 'isPersistent' &&
|
|
!target.constructor.Interface.hasOwnProperty(prop) &&
|
|
shouldBeReleasedProperties.indexOf(prop) === -1
|
|
) {
|
|
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.',
|
|
);
|
|
didWarnForAddedNewProperty = true;
|
|
}
|
|
target[prop] = value;
|
|
return true;
|
|
},
|
|
});
|
|
},
|
|
});
|
|
/*eslint-enable no-func-assign */
|
|
}
|
|
}
|
|
|
|
addEventPoolingTo(SyntheticEvent);
|
|
|
|
/**
|
|
* Helper to nullify syntheticEvent instance properties when destructing
|
|
*
|
|
* @param {String} propName
|
|
* @param {?object} getVal
|
|
* @return {object} defineProperty object
|
|
*/
|
|
function getPooledWarningPropertyDefinition(propName, getVal) {
|
|
const isFunction = typeof getVal === 'function';
|
|
return {
|
|
configurable: true,
|
|
set: set,
|
|
get: get,
|
|
};
|
|
|
|
function set(val) {
|
|
const action = isFunction ? 'setting the method' : 'setting the property';
|
|
warn(action, 'This is effectively a no-op');
|
|
return val;
|
|
}
|
|
|
|
function get() {
|
|
const action = isFunction
|
|
? 'accessing the method'
|
|
: 'accessing the property';
|
|
const result = isFunction
|
|
? 'This is a no-op function'
|
|
: 'This is set to null';
|
|
warn(action, result);
|
|
return getVal;
|
|
}
|
|
|
|
function warn(action, result) {
|
|
const warningCondition = false;
|
|
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,
|
|
);
|
|
}
|
|
}
|
|
|
|
function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
|
|
const EventConstructor = this;
|
|
if (EventConstructor.eventPool.length) {
|
|
const instance = EventConstructor.eventPool.pop();
|
|
EventConstructor.call(
|
|
instance,
|
|
dispatchConfig,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeInst,
|
|
);
|
|
return instance;
|
|
}
|
|
return new EventConstructor(
|
|
dispatchConfig,
|
|
targetInst,
|
|
nativeEvent,
|
|
nativeInst,
|
|
);
|
|
}
|
|
|
|
function releasePooledEvent(event) {
|
|
const EventConstructor = this;
|
|
invariant(
|
|
event instanceof EventConstructor,
|
|
'Trying to release an event instance into a pool of a different type.',
|
|
);
|
|
event.destructor();
|
|
if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
|
|
EventConstructor.eventPool.push(event);
|
|
}
|
|
}
|
|
|
|
function addEventPoolingTo(EventConstructor) {
|
|
EventConstructor.eventPool = [];
|
|
EventConstructor.getPooled = getPooledEvent;
|
|
EventConstructor.release = releasePooledEvent;
|
|
}
|
|
|
|
export default SyntheticEvent;
|