blob: 8cef915b1a368165b996fdbc649dccefce100ac4 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Interface for registration of listeners of events that can
* wake the extension. All EventListeners must invoke
* |addOnStartup| in the first event loop in order to
* properly receive events that woke the extension. When adding a new
* EventListener, be sure to also add it to |mr.Init.addEventListeners_|.
*/
goog.provide('mr.EventListener');
goog.require('mr.EventAnalytics');
goog.require('mr.Module');
goog.require('mr.PersistentData');
goog.require('mr.PersistentDataManager');
/**
* Listens for an extension event conditionally, and forwards the event to a
* designated module.
*
* This is useful for event listeners that are not added when the extension
* initially starts up (mDNS listeners, for instance). When an event
* listener is ready to be added for the first time (and the module is ready
* to handle the event), |addListener()| should be called. By doing so, the
* event listener will be automatically added back at the top level the next
* time the extension wakes up from suspension, unless |removeListener()| is
* called.
*
* @implements {mr.PersistentData}
* @template EVENT
*/
mr.EventListener = class {
/**
* @param {!mr.EventAnalytics.Event} eventType The event type for this
* listener to record with analytics.
* @param {string} name Name of the handler for PersistentData.
* @param {mr.ModuleId} moduleId Name of the module handling the events.
* @param {!EVENT} eventHandler The event handler to listen to.
* @param {...*} listenerArgs Additional arguments when adding the listener,
* such as filters.
*/
constructor(eventType, name, moduleId, eventHandler, ...listenerArgs) {
/** @private @const {!mr.EventAnalytics.Event} */
this.eventType_ = eventType;
/** @private @const {string} */
this.name_ = name;
/** @private @const {mr.ModuleId} */
this.moduleId_ = moduleId;
/** @private @const {!EVENT} */
this.eventHandler_ = eventHandler;
/** @private @const {!Array<*>} */
this.listenerArgs_ = listenerArgs;
/**
* This field is stored as temporary data. Set to true if the listener was
* added, and will be added back the next time the extension wakes up via
* |addOnStartup()|.
* @private {boolean}
*/
this.hasListener_ = false;
/** @private @const {?function(*)} */
this.listener_ = (...args) => this.dispatchEvent_(...args);
}
/**
* Adds back the event listener that was added before the last suspension.
* This method is called by mr.Init during the first event loop only.
*/
addOnStartup() {
mr.PersistentDataManager.register(this);
}
/**
* Helper method to add the listener to the event.
* @private
*/
doAddListener_() {
this.eventHandler_.addListener(this.listener_, ...this.listenerArgs_);
}
/**
* Adds event listener. No-ops if listener is already added. Event
* listeners will be added back automatically the next time extension wakes
* up, during |addOnStartup|.
*/
addListener() {
if (this.hasListener_) {
return;
}
this.hasListener_ = true;
this.doAddListener_();
}
/**
* Removes event listener. The next time extension wakes up, the event
* listener will not be added back during |addOnStartup|.
*/
removeListener() {
if (!this.hasListener_) {
return;
}
this.eventHandler_.removeListener(this.listener_);
this.hasListener_ = false;
}
/**
* Implementations may override this method validate an incoming event before
* it is forwarded to the designated module.
* @param {...*} args
* @return {boolean} true if the event can be forwarded to the module.
*/
validateEvent(...args) {
return true;
}
/**
* The entry point for incoming events. First the event will be validated.
* After that, the event will be forwarded to the module, which is loaded if
* necessary. Since asynchronous event handling is required, a value
* representing asynchronous handling will be returned synchronously, as
* required by the event.
* @param {...*} args Parameters for the incoming event.
* @return {*} false if the event is invalid. Otherwise, a value that
* represents that event will be handled asynchronously will be returned.
* @private
*/
dispatchEvent_(...args) {
mr.EventAnalytics.recordEvent(this.eventType_);
if (!this.validateEvent(...args)) {
return false;
}
mr.Module.load(this.moduleId_)
.then(module => module.handleEvent(this.eventHandler_, ...args));
return this.deferredReturnValue();
}
/**
* Returns |true| if the event listener is already registered.
* @return {boolean}
*/
isRegistered() {
return this.hasListener_;
}
/**
* @override
*/
getStorageKey() {
return 'mr.EventListener.' + this.name_;
}
/**
* @override
*/
getData() {
return [this.hasListener_];
}
/**
* @override
*/
loadSavedData() {
const hadListener =
/** @type {boolean} */ (
mr.PersistentDataManager.getTemporaryData(this));
if (hadListener) {
this.addListener();
}
}
/**
* Implementations may override this method to provide a return value in the
* case the event is not handled synchronously. The default value is
* undefined.
* @return {*}
*/
deferredReturnValue() {}
};