Fixed NativeEventListener deregistration

Summary:
The `EmitterSubscription.remove()` method was previously calling `this.subscriber.removeSubscription(this)` directly, bypassing the mechanism in `NativeEventEmitter` that keeps track of the number of subscriptions.

This meant that native event modules (subclasses of `RCTEventEmitter`) would keep sending events even after all the listeners had been removed. This wasn't a huge overhead, since these modules are singletons and only send one message over the bridge per event, regardless of the number of listeners, but it's still undesirable.

This fixes the problem by routing the `EmitterSubscription.remove()` method through the `EventEmitter` so that `NativeEventEmitter` can apply the additional native calls.

I've also improved the architecture so that each `NativeEventEmitter` uses its own `EventEmitter`, but they currently all still share the same `EventSubscriptionVendor` so that legacy code which registers events via `RCTDeviceEventEmitter` still works.

Reviewed By: vjeux

Differential Revision: D3292361

fbshipit-source-id: d60e881d50351523d2112473703bea826641cdef
This commit is contained in:
Nick Lockwood 2016-05-16 04:04:37 -07:00 committed by Facebook Github Bot 1
parent 9a899bed65
commit 516bf7bd94
15 changed files with 306 additions and 254 deletions

View File

@ -14,7 +14,7 @@
const BugReportingNativeModule = require('NativeModules').BugReporting;
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
import type {EmitterSubscription} from 'EventEmitter';
import type EmitterSubscription from 'EmitterSubscription';
type ExtraData = { [key: string]: string };
type SourceCallback = () => string;
@ -28,7 +28,7 @@ type SourceCallback = () => string;
class BugReporting {
static _sources: Map<string, SourceCallback> = new Map();
static _subscription: EmitterSubscription = null;
static _subscription: ?EmitterSubscription = null;
/**
* `init` is called in `AppRegistry.runApplication`, so you shouldn't have to worry about it.

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2015-present, 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 EmitterSubscription
* @noflow
* @typechecks
*/
'use strict';
const EventSubscription = require('EventSubscription');
import type EventEmitter from 'EventEmitter';
import type EventSubscriptionVendor from 'EventSubscriptionVendor';
/**
* EmitterSubscription represents a subscription with listener and context data.
*/
class EmitterSubscription extends EventSubscription {
emitter: EventEmitter;
listener: Function;
context: ?Object;
/**
* @param {EventEmitter} emitter - The event emitter that registered this
* subscription
* @param {EventSubscriptionVendor} subscriber - The subscriber that controls
* this subscription
* @param {function} listener - Function to invoke when the specified event is
* emitted
* @param {*} context - Optional context object to use when invoking the
* listener
*/
constructor(
emitter: EventEmitter,
subscriber: EventSubscriptionVendor,
listener: Function,
context: ?Object
) {
super(subscriber);
this.emitter = emitter;
this.listener = listener;
this.context = context;
}
/**
* Removes this subscription from the emitter that registered it.
* Note: we're overriding the `remove()` method of EventSubscription here
* but deliberately not calling `super.remove()` as the responsibility
* for removing the subscription lies with the EventEmitter.
*/
remove() {
this.emitter.removeSubscription(this);
}
}
module.exports = EmitterSubscription;

View File

@ -8,14 +8,13 @@
*
* @providesModule EventEmitter
* @noflow
* @typechecks
* @typecheck
*/
var EmitterSubscription = require('EmitterSubscription');
var ErrorUtils = require('ErrorUtils');
var EventSubscriptionVendor = require('EventSubscriptionVendor');
var emptyFunction = require('fbjs/lib/emptyFunction');
var invariant = require('fbjs/lib/invariant');
const EmitterSubscription = require('EmitterSubscription');
const EventSubscriptionVendor = require('EventSubscriptionVendor');
const emptyFunction = require('fbjs/lib/emptyFunction');
const invariant = require('fbjs/lib/invariant');
/**
* @class EventEmitter
@ -31,11 +30,18 @@ var invariant = require('fbjs/lib/invariant');
* more advanced emitter may use an EventHolder and EventFactory.
*/
class EventEmitter {
_subscriber: EventSubscriptionVendor;
_currentSubscription: ?EmitterSubscription;
/**
* @constructor
*
* @param {EventSubscriptionVendor} subscriber - Optional subscriber instance
* to use. If omitted, a new subscriber will be created for the emitter.
*/
constructor() {
this._subscriber = new EventSubscriptionVendor();
constructor(subscriber: ?EventSubscriptionVendor) {
this._subscriber = subscriber || new EventSubscriptionVendor();
}
/**
@ -53,10 +59,12 @@ class EventEmitter {
* listener
*/
addListener(
eventType: String, listener, context: ?Object): EmitterSubscription {
return this._subscriber.addSubscription(
eventType: string, listener: Function, context: ?Object): EmitterSubscription {
return (this._subscriber.addSubscription(
eventType,
new EmitterSubscription(this._subscriber, listener, context));
new EmitterSubscription(this, this._subscriber, listener, context)
) : any);
}
/**
@ -69,10 +77,9 @@ class EventEmitter {
* @param {*} context - Optional context object to use when invoking the
* listener
*/
once(eventType: String, listener, context: ?Object): EmitterSubscription {
var emitter = this;
return this.addListener(eventType, function() {
emitter.removeCurrentListener();
once(eventType: string, listener: Function, context: ?Object): EmitterSubscription {
return this.addListener(eventType, () => {
this.removeCurrentListener();
listener.apply(context, arguments);
});
}
@ -84,7 +91,7 @@ class EventEmitter {
* @param {?string} eventType - Optional name of the event whose registered
* listeners to remove
*/
removeAllListeners(eventType: ?String) {
removeAllListeners(eventType: ?string) {
this._subscriber.removeAllSubscriptions(eventType);
}
@ -114,7 +121,19 @@ class EventEmitter {
!!this._currentSubscription,
'Not in an emitting cycle; there is no current subscription'
);
this._subscriber.removeSubscription(this._currentSubscription);
this.removeSubscription(this._currentSubscription);
}
/**
* Removes a specific subscription. Called by the `remove()` method of the
* subscription itself to ensure any necessary cleanup is performed.
*/
removeSubscription(subscription: EmitterSubscription) {
invariant(
subscription.emitter === this,
'Subscription does not belong to this emitter.'
);
this._subscriber.removeSubscription(subscription);
}
/**
@ -124,8 +143,8 @@ class EventEmitter {
* @param {string} eventType - Name of the event to query
* @returns {array}
*/
listeners(eventType: String): Array /* TODO: Array<EventSubscription> */ {
var subscriptions = this._subscriber.getSubscriptionsForType(eventType);
listeners(eventType: string): [EmitterSubscription] {
const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any);
return subscriptions
? subscriptions.filter(emptyFunction.thatReturnsTrue).map(
function(subscription) {
@ -148,13 +167,11 @@ class EventEmitter {
*
* emitter.emit('someEvent', 'abc'); // logs 'abc'
*/
emit(eventType: String) {
var subscriptions = this._subscriber.getSubscriptionsForType(eventType);
emit(eventType: string) {
const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any);
if (subscriptions) {
var keys = Object.keys(subscriptions);
for (var ii = 0; ii < keys.length; ii++) {
var key = keys[ii];
var subscription = subscriptions[key];
for (let i = 0, l = subscriptions.length; i < l; i++) {
const subscription = subscriptions[i];
// The subscription may have been removed during this event loop.
if (subscription) {

View File

@ -1,22 +1,20 @@
/**
* @generated SignedSource<<fb2bb5c1c402a097a7e1da7413526629>>
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in of a static_upstream project! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
* !! the latest version from upstream. !!
* !! 2) Make your changes, test them, etc. !!
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
* !! static_upstream. !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* 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 EventEmitterWithHolding
* @typechecks
* @flow
*/
'use strict';
import type EmitterSubscription from 'EmitterSubscription';
import type EventEmitter from 'EventEmitter';
import type EventHolder from 'EventHolder';
/**
* @class EventEmitterWithHolding
* @description
@ -30,6 +28,12 @@
* that uses an emitter.
*/
class EventEmitterWithHolding {
_emitter: EventEmitter;
_eventHolder: EventHolder;
_currentEventToken: ?Object;
_emittingHeldEvents: boolean;
/**
* @constructor
* @param {object} emitter - The object responsible for emitting the actual
@ -37,7 +41,7 @@ class EventEmitterWithHolding {
* @param {object} holder - The event holder that is responsible for holding
* and then emitting held events.
*/
constructor(emitter, holder) {
constructor(emitter: EventEmitter, holder: EventHolder) {
this._emitter = emitter;
this._eventHolder = holder;
this._currentEventToken = null;
@ -47,14 +51,14 @@ class EventEmitterWithHolding {
/**
* @see EventEmitter#addListener
*/
addListener(eventType: String, listener, context: ?Object) {
addListener(eventType: string, listener: Function, context: ?Object) {
return this._emitter.addListener(eventType, listener, context);
}
/**
* @see EventEmitter#once
*/
once(eventType: String, listener, context: ?Object) {
once(eventType: string, listener: Function, context: ?Object) {
return this._emitter.once(eventType, listener, context);
}
@ -79,8 +83,8 @@ class EventEmitterWithHolding {
* }); // logs 'abc'
*/
addRetroactiveListener(
eventType: String, listener, context: ?Object): EmitterSubscription {
var subscription = this._emitter.addListener(eventType, listener, context);
eventType: string, listener: Function, context: ?Object): EmitterSubscription {
const subscription = this._emitter.addListener(eventType, listener, context);
this._emittingHeldEvents = true;
this._eventHolder.emitToListener(eventType, listener, context);
@ -92,7 +96,7 @@ class EventEmitterWithHolding {
/**
* @see EventEmitter#removeAllListeners
*/
removeAllListeners(eventType: String) {
removeAllListeners(eventType: string) {
this._emitter.removeAllListeners(eventType);
}
@ -106,15 +110,15 @@ class EventEmitterWithHolding {
/**
* @see EventEmitter#listeners
*/
listeners(eventType: String) /* TODO: Annotate return type here */ {
listeners(eventType: string) /* TODO: Annotate return type here */ {
return this._emitter.listeners(eventType);
}
/**
* @see EventEmitter#emit
*/
emit(eventType: String, a, b, c, d, e, _) {
this._emitter.emit(eventType, a, b, c, d, e, _);
emit(eventType: string, ...args: any) {
this._emitter.emit(eventType, ...args);
}
/**
@ -132,12 +136,9 @@ class EventEmitterWithHolding {
* console.log(message);
* }); // logs 'abc'
*/
emitAndHold(eventType: String, a, b, c, d, e, _) {
this._currentEventToken = this._eventHolder.holdEvent(
eventType,
a, b, c, d, e, _
);
this._emitter.emit(eventType, a, b, c, d, e, _);
emitAndHold(eventType: string, ...args: any) {
this._currentEventToken = this._eventHolder.holdEvent(eventType, ...args);
this._emitter.emit(eventType, ...args);
this._currentEventToken = null;
}
@ -145,7 +146,7 @@ class EventEmitterWithHolding {
* @see EventHolder#releaseCurrentEvent
*/
releaseCurrentEvent() {
if (this._currentEventToken !== null) {
if (this._currentEventToken) {
this._eventHolder.releaseEvent(this._currentEventToken);
} else if (this._emittingHeldEvents) {
this._eventHolder.releaseCurrentEvent();
@ -156,7 +157,7 @@ class EventEmitterWithHolding {
* @see EventHolder#releaseEventType
* @param {string} eventType
*/
releaseHeldEventType(eventType: String) {
releaseHeldEventType(eventType: string) {
this._eventHolder.releaseEventType(eventType);
}
}

View File

@ -1,25 +1,23 @@
/**
* @generated SignedSource<<0591836c443c735d24e61782320d3d16>>
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in of a static_upstream project! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
* !! the latest version from upstream. !!
* !! 2) Make your changes, test them, etc. !!
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
* !! static_upstream. !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* 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 EventHolder
* @typechecks
* @flow
*/
'use strict';
var invariant = require('fbjs/lib/invariant');
const invariant = require('fbjs/lib/invariant');
class EventHolder {
_heldEvents: Object;
_currentEventKey: ?Object;
constructor() {
this._heldEvents = {};
this._currentEventKey = null;
@ -46,14 +44,14 @@ class EventHolder {
* }); //logs 'abc'
*
*/
holdEvent(eventType: String, a, b, c, d, e, _) {
holdEvent(eventType: string, ...args: any) {
this._heldEvents[eventType] = this._heldEvents[eventType] || [];
var eventsOfType = this._heldEvents[eventType];
var key = {
const eventsOfType = this._heldEvents[eventType];
const key = {
eventType: eventType,
index: eventsOfType.length
};
eventsOfType.push([a, b, c, d, e, _]);
eventsOfType.push(args);
return key;
}
@ -65,12 +63,12 @@ class EventHolder {
* @param {?object} context - Optional context object to use when invoking
* the listener
*/
emitToListener(eventType: ?String , listener, context: ?Object) {
var eventsOfType = this._heldEvents[eventType];
emitToListener(eventType: ?string , listener: Function, context: ?Object) {
const eventsOfType = this._heldEvents[eventType];
if (!eventsOfType) {
return;
}
var origEventKey = this._currentEventKey;
const origEventKey = this._currentEventKey;
eventsOfType.forEach((/*?array*/ eventHeld, /*number*/ index) => {
if (!eventHeld) {
return;
@ -97,7 +95,7 @@ class EventHolder {
this._currentEventKey !== null,
'Not in an emitting cycle; there is no current event'
);
this.releaseEvent(this._currentEventKey);
this._currentEventKey && this.releaseEvent(this._currentEventKey);
}
/**
@ -115,7 +113,7 @@ class EventHolder {
*
* @param {string} type
*/
releaseEventType(type: String) {
releaseEventType(type: string) {
this._heldEvents[type] = [];
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2015-present, 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 EventSubscription
* @flow
*/
'use strict';
import type EventSubscriptionVendor from 'EventSubscriptionVendor';
/**
* EventSubscription represents a subscription to a particular event. It can
* remove its own subscription.
*/
class EventSubscription {
eventType: string;
key: number;
subscriber: EventSubscriptionVendor;
/**
* @param {EventSubscriptionVendor} subscriber the subscriber that controls
* this subscription.
*/
constructor(subscriber: EventSubscriptionVendor) {
this.subscriber = subscriber;
}
/**
* Removes this subscription from the subscriber that controls it.
*/
remove() {
this.subscriber.removeSubscription(this);
}
}
module.exports = EventSubscription;

View File

@ -1,23 +1,19 @@
/**
* @generated SignedSource<<24d5cc1cdd24704296686faf89dd36cf>>
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in of a static_upstream project! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
* !! the latest version from upstream. !!
* !! 2) Make your changes, test them, etc. !!
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
* !! static_upstream. !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* 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 EventSubscriptionVendor
* @typechecks
* @flow
*/
'use strict';
var invariant = require('fbjs/lib/invariant');
const invariant = require('fbjs/lib/invariant');
import type EventSubscription from 'EventSubscription';
/**
* EventSubscriptionVendor stores a set of EventSubscriptions that are
@ -25,6 +21,9 @@ var invariant = require('fbjs/lib/invariant');
*/
class EventSubscriptionVendor {
_subscriptionsForType: Object;
_currentSubscription: ?EventSubscription;
constructor() {
this._subscriptionsForType = {};
this._currentSubscription = null;
@ -37,14 +36,14 @@ class EventSubscriptionVendor {
* @param {EventSubscription} subscription
*/
addSubscription(
eventType: String, subscription: EventSubscription): EventSubscription {
eventType: string, subscription: EventSubscription): EventSubscription {
invariant(
subscription.subscriber === this,
'The subscriber of the subscription is incorrectly set.');
if (!this._subscriptionsForType[eventType]) {
this._subscriptionsForType[eventType] = [];
}
var key = this._subscriptionsForType[eventType].length;
const key = this._subscriptionsForType[eventType].length;
this._subscriptionsForType[eventType].push(subscription);
subscription.eventType = eventType;
subscription.key = key;
@ -57,7 +56,7 @@ class EventSubscriptionVendor {
* @param {?string} eventType - Optional name of the event type whose
* registered supscriptions to remove, if null remove all subscriptions.
*/
removeAllSubscriptions(eventType: ?String) {
removeAllSubscriptions(eventType: ?string) {
if (eventType === undefined) {
this._subscriptionsForType = {};
} else {
@ -72,10 +71,10 @@ class EventSubscriptionVendor {
* @param {object} subscription
*/
removeSubscription(subscription: Object) {
var eventType = subscription.eventType;
var key = subscription.key;
const eventType = subscription.eventType;
const key = subscription.key;
var subscriptionsForType = this._subscriptionsForType[eventType];
const subscriptionsForType = this._subscriptionsForType[eventType];
if (subscriptionsForType) {
delete subscriptionsForType[key];
}
@ -93,7 +92,7 @@ class EventSubscriptionVendor {
* @param {string} eventType
* @returns {?array}
*/
getSubscriptionsForType(eventType: String): ?Array {
getSubscriptionsForType(eventType: string): ?[EventSubscription] {
return this._subscriptionsForType[eventType];
}
}

View File

@ -1,22 +1,17 @@
/**
* @generated SignedSource<<7149bdac6fb48595f245ad6e76938e44>>
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in of a static_upstream project! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
* !! the latest version from upstream. !!
* !! 2) Make your changes, test them, etc. !!
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
* !! static_upstream. !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* 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 EventValidator
* @flow
*/
'use strict';
var copyProperties = require('copyProperties');
const copyProperties = require('copyProperties');
/**
* EventValidator is designed to validate event types to make it easier to catch
@ -27,7 +22,7 @@ var copyProperties = require('copyProperties');
* mistyped the event name it will suggest what you might have meant to type in
* the error message.
*/
var EventValidator = {
const EventValidator = {
/**
* @param {Object} emitter - The object responsible for emitting the actual
* events
@ -35,12 +30,12 @@ var EventValidator = {
* check for errors
* @return {Object} A new emitter with event type validation
* @example
* var types = {someEvent: true, anotherEvent: true};
* var emitter = EventValidator.addValidation(emitter, types);
* const types = {someEvent: true, anotherEvent: true};
* const emitter = EventValidator.addValidation(emitter, types);
*/
addValidation: function(emitter: Object, types: Object) {
var eventTypes = Object.keys(types);
var emitterWithValidation = Object.create(emitter);
const eventTypes = Object.keys(types);
const emitterWithValidation = Object.create(emitter);
copyProperties(emitterWithValidation, {
emit: function emit(type, a, b, c, d, e, _) {
@ -60,7 +55,7 @@ function assertAllowsEventType(type, allowedTypes) {
}
function errorMessageFor(type, allowedTypes) {
var message = 'Unknown event type "' + type + '". ';
let message = 'Unknown event type "' + type + '". ';
if (__DEV__) {
message += recommendationFor(type, allowedTypes);
}
@ -71,7 +66,7 @@ function errorMessageFor(type, allowedTypes) {
// Allow for good error messages
if (__DEV__) {
var recommendationFor = function (type, allowedTypes) {
var closestTypeRecommendation = closestTypeFor(type, allowedTypes);
const closestTypeRecommendation = closestTypeFor(type, allowedTypes);
if (isCloseEnough(closestTypeRecommendation, type)) {
return 'Did you mean "' + closestTypeRecommendation.type + '"? ';
} else {
@ -80,7 +75,7 @@ if (__DEV__) {
};
var closestTypeFor = function (type, allowedTypes) {
var typeRecommendations = allowedTypes.map(
const typeRecommendations = allowedTypes.map(
typeRecommendationFor.bind(this, type)
);
return typeRecommendations.sort(recommendationSort)[0];
@ -108,8 +103,8 @@ if (__DEV__) {
};
var damerauLevenshteinDistance = function (a, b) {
var i, j;
var d = [];
let i, j;
const d = [];
for (i = 0; i <= a.length; i++) {
d[i] = [i];
@ -121,7 +116,7 @@ if (__DEV__) {
for (i = 1; i <= a.length; i++) {
for (j = 1; j <= b.length; j++) {
var cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;
const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;
d[i][j] = Math.min(
d[i - 1][j] + 1,
@ -130,8 +125,8 @@ if (__DEV__) {
);
if (i > 1 && j > 1 &&
a.charAt(i - 1) == b.charAt(j - 2) &&
a.charAt(i - 2) == b.charAt(j - 1)) {
a.charAt(i - 1) === b.charAt(j - 2) &&
a.charAt(i - 2) === b.charAt(j - 1)) {
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost);
}
}

View File

@ -11,6 +11,7 @@
*/
'use strict';
const EventEmitter = require('EventEmitter');
const Platform = require('Platform');
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
const invariant = require('fbjs/lib/invariant');
@ -21,45 +22,39 @@ import type EmitterSubscription from 'EmitterSubscription';
* Abstract base class for implementing event-emitting modules. This implements
* a subset of the standard EventEmitter node module API.
*/
class NativeEventEmitter {
class NativeEventEmitter extends EventEmitter {
_nativeModule: Object;
constructor(nativeModule: Object) {
super(RCTDeviceEventEmitter.sharedSubscriber);
if (Platform.OS === 'ios') {
invariant(nativeModule, 'Native module cannot be null.');
this._nativeModule = nativeModule;
}
}
addListener(eventType: string, listener: any, context: ?Object): EmitterSubscription {
addListener(eventType: string, listener: Function, context: ?Object): EmitterSubscription {
if (Platform.OS === 'ios') {
this._nativeModule.addListener(eventType);
}
return RCTDeviceEventEmitter.nativeAddListener(eventType, listener, context);
}
once(eventType: string, listener: any, context: ?Object): EmitterSubscription {
return this.addListener(eventType, () => {
this.removeCurrentListener();
listener.apply(context, arguments);
});
return super.addListener(eventType, listener, context);
}
removeAllListeners(eventType: string) {
invariant(eventType, 'eventType argument is required.');
if (Platform.OS === 'ios') {
const count = RCTDeviceEventEmitter.listeners(eventType).length;
const count = this.listeners(eventType).length;
this._nativeModule.removeListeners(count);
}
RCTDeviceEventEmitter.removeAllListeners(eventType);
super.removeAllListeners(eventType);
}
removeCurrentListener() {
removeSubscription(subscription: EmitterSubscription) {
if (Platform.OS === 'ios') {
this._nativeModule.removeListeners(1);
}
RCTDeviceEventEmitter.removeCurrentListener();
super.removeSubscription(subscription);
}
}

View File

@ -12,6 +12,7 @@
'use strict';
const EventEmitter = require('EventEmitter');
const EventSubscriptionVendor = require('EventSubscriptionVendor');
const BatchedBridge = require('BatchedBridge');
import type EmitterSubscription from 'EmitterSubscription';
@ -22,7 +23,15 @@ import type EmitterSubscription from 'EmitterSubscription';
*/
class RCTDeviceEventEmitter extends EventEmitter {
addListener(eventType: string, listener: any, context: ?Object): EmitterSubscription {
sharedSubscriber: EventSubscriptionVendor;
constructor() {
const sharedSubscriber = new EventSubscriptionVendor();
super(sharedSubscriber);
this.sharedSubscriber = sharedSubscriber;
}
addListener(eventType: string, listener: Function, context: ?Object): EmitterSubscription {
if (eventType.lastIndexOf('statusBar', 0) === 0) {
console.warn('`%s` event should be registered via the StatusBarIOS module', eventType);
return require('StatusBarIOS').addListener(eventType, listener, context);
@ -34,8 +43,26 @@ class RCTDeviceEventEmitter extends EventEmitter {
return super.addListener(eventType, listener, context);
}
nativeAddListener(eventType: string, listener: any, context: ?Object): EmitterSubscription {
return super.addListener(eventType, listener, context);
removeAllListeners(eventType: ?string) {
if (eventType) {
if (eventType.lastIndexOf('statusBar', 0) === 0) {
console.warn('statusBar events should be unregistered via the StatusBarIOS module');
return require('StatusBarIOS').removeAllListeners(eventType);
}
if (eventType.lastIndexOf('keyboard', 0) === 0) {
console.warn('keyboard events should be unregistered via the Keyboard module');
return require('Keyboard').removeAllListeners(eventType);
}
}
super.removeAllListeners(eventType);
}
removeSubscription(subscription: EmitterSubscription) {
if (subscription.emitter !== this) {
subscription.emitter.removeSubscription(subscription);
} else {
super.removeSubscription(subscription);
}
}
}

View File

@ -11,10 +11,14 @@
*/
'use strict';
var BatchedBridge = require('BatchedBridge');
var EventEmitter = require('EventEmitter');
const BatchedBridge = require('BatchedBridge');
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var RCTNativeAppEventEmitter = new EventEmitter();
/**
* Deprecated - subclass NativeEventEmitter to create granular event modules instead of
* adding all event listeners directly to RCTNativeAppEventEmitter.
*/
const RCTNativeAppEventEmitter = RCTDeviceEventEmitter;
BatchedBridge.registerCallableModule(
'RCTNativeAppEventEmitter',

View File

@ -1,30 +1,25 @@
/**
* @generated SignedSource<<c735038726af2daf584b3e7fb3950e8b>>
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in of a static_upstream project! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
* !! the latest version from upstream. !!
* !! 2) Make your changes, test them, etc. !!
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
* !! static_upstream. !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* 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 mixInEventEmitter
* @flow
*/
var EventEmitter = require('EventEmitter');
var EventEmitterWithHolding = require('EventEmitterWithHolding');
var EventHolder = require('EventHolder');
var EventValidator = require('EventValidator');
const EventEmitter = require('EventEmitter');
const EventEmitterWithHolding = require('EventEmitterWithHolding');
const EventHolder = require('EventHolder');
const EventValidator = require('EventValidator');
var copyProperties = require('copyProperties');
var invariant = require('fbjs/lib/invariant');
var keyOf = require('fbjs/lib/keyOf');
const copyProperties = require('copyProperties');
const invariant = require('fbjs/lib/invariant');
const keyOf = require('fbjs/lib/keyOf');
var TYPES_KEY = keyOf({__types: true});
const TYPES_KEY = keyOf({__types: true});
/**
* API to setup an object or constructor to be able to emit data events.
@ -47,16 +42,16 @@ var TYPES_KEY = keyOf({__types: true});
* mixInEventEmitter(Singleton, {lonely: true});
* Singleton.emit('lonely', true);
*/
function mixInEventEmitter(klass, types) {
function mixInEventEmitter(cls: Function | Object, types: Object) {
invariant(types, 'Must supply set of valid event types');
// If this is a constructor, write to the prototype, otherwise write to the
// singleton object.
var target = klass.prototype || klass;
const target = cls.prototype || cls;
invariant(!target.__eventEmitter, 'An active emitter is already mixed in');
var ctor = klass.constructor;
const ctor = cls.constructor;
if (ctor) {
invariant(
ctor === Object || ctor === Function,
@ -76,7 +71,7 @@ function mixInEventEmitter(klass, types) {
copyProperties(target, EventEmitterMixin);
}
var EventEmitterMixin = {
const EventEmitterMixin = {
emit: function(eventType, a, b, c, d, e, _) {
return this.__getEventEmitter().emit(eventType, a, b, c, d, e, _);
},
@ -123,10 +118,10 @@ var EventEmitterMixin = {
__getEventEmitter: function() {
if (!this.__eventEmitter) {
var emitter = new EventEmitter();
let emitter = new EventEmitter();
emitter = EventValidator.addValidation(emitter, this.__types);
var holder = new EventHolder();
const holder = new EventHolder();
this.__eventEmitter = new EventEmitterWithHolding(emitter, holder);
}
return this.__eventEmitter;

View File

@ -1,43 +0,0 @@
/**
* @generated SignedSource<<d17b6e5d9b7118fb0ed9169f579e5b8a>>
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in of a static_upstream project! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
* !! the latest version from upstream. !!
* !! 2) Make your changes, test them, etc. !!
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
* !! static_upstream. !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* @providesModule EmitterSubscription
* @noflow
* @typechecks
*/
'use strict';
var EventSubscription = require('EventSubscription');
/**
* EmitterSubscription represents a subscription with listener and context data.
*/
class EmitterSubscription extends EventSubscription {
/**
* @param {EventSubscriptionVendor} subscriber - The subscriber that controls
* this subscription
* @param {function} listener - Function to invoke when the specified event is
* emitted
* @param {*} context - Optional context object to use when invoking the
* listener
*/
constructor(subscriber: EventSubscriptionVendor, listener, context: ?Object) {
super(subscriber);
this.listener = listener;
this.context = context;
}
}
module.exports = EmitterSubscription;

View File

@ -1,42 +0,0 @@
/**
* @generated SignedSource<<92108a17b1f3eee4b7e3dd7d484aa17a>>
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in of a static_upstream project! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
* !! the latest version from upstream. !!
* !! 2) Make your changes, test them, etc. !!
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
* !! static_upstream. !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* @providesModule EventSubscription
* @typechecks
*/
'use strict';
/**
* EventSubscription represents a subscription to a particular event. It can
* remove its own subscription.
*/
class EventSubscription {
/**
* @param {EventSubscriptionVendor} subscriber the subscriber that controls
* this subscription.
*/
constructor(subscriber: EventSubscriptionVendor) {
this.subscriber = subscriber;
}
/**
* Removes this subscription from the subscriber that controls it.
*/
remove() {
this.subscriber.removeSubscription(this);
}
}
module.exports = EventSubscription;

View File

@ -72,7 +72,9 @@ RCT_EXPORT_METHOD(addListener:(NSString *)eventName)
RCT_EXPORT_METHOD(removeListeners:(NSInteger)count)
{
RCTAssert(count <= _listenerCount, @"Attempted to remove more listeners than added");
if (RCT_DEBUG && count > _listenerCount) {
RCTLogError(@"Attempted to remove more %@ listeners than added", [self class]);
}
if (count == _listenerCount) {
[self stopObserving];
}