/**
* @license
* Copyright 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.util.FakeEventTarget');
goog.require('shaka.asserts');
goog.require('shaka.util.MultiMap');
/**
* A work-alike for EventTarget. Only DOM elements may be true EventTargets,
* but this can be used as a base class to provide event dispatch to non-DOM
* classes.
*
* @param {shaka.util.FakeEventTarget} parent The parent for the purposes of
* event bubbling. Note that events on a FakeEventTarget can only bubble
* to other FakeEventTargets.
* @struct
* @constructor
* @implements {EventTarget}
* @export
*/
shaka.util.FakeEventTarget = function(parent) {
/**
* @private {!shaka.util.MultiMap.<shaka.util.FakeEventTarget.ListenerType>}
*/
this.listeners_ = new shaka.util.MultiMap();
/** @protected {shaka.util.FakeEventTarget} */
this.parent = parent;
};
/**
* These are the listener types defined in the closure extern for EventTarget.
* @typedef {EventListener|function(!Event):(boolean|undefined)}
*/
shaka.util.FakeEventTarget.ListenerType;
/**
* Add an event listener to this object.
*
* @param {string} type The event type to listen for.
* @param {shaka.util.FakeEventTarget.ListenerType} listener The callback or
* listener object to invoke.
* @param {boolean=} opt_capturing True to listen during the capturing phase,
* false to listen during the bubbling phase. Note that FakeEventTarget
* does not support the capturing phase from the standard event model.
* @override
*/
shaka.util.FakeEventTarget.prototype.addEventListener =
function(type, listener, opt_capturing) {
// We don't support the capturing phase.
shaka.asserts.assert(!opt_capturing);
if (!opt_capturing) {
this.listeners_.push(type, listener);
}
};
/**
* Remove an event listener from this object.
*
* @param {string} type The event type for which you wish to remove a listener.
* @param {shaka.util.FakeEventTarget.ListenerType} listener The callback or
* listener object to remove.
* @param {boolean=} opt_capturing True to remove a listener for the capturing
* phase, false to remove a listener for the bubbling phase. Note that
* FakeEventTarget does not support the capturing phase from the standard
* event model.
* @override
*/
shaka.util.FakeEventTarget.prototype.removeEventListener =
function(type, listener, opt_capturing) {
// We don't support the capturing phase.
shaka.asserts.assert(!opt_capturing);
if (!opt_capturing) {
this.listeners_.remove(type, listener);
}
};
/**
* Dispatch an event from this object.
*
* @param {!Event} event The event to be dispatched from this object.
* @return {boolean} True if the default action was prevented.
* @override
*/
shaka.util.FakeEventTarget.prototype.dispatchEvent = function(event) {
// Overwrite the Event's properties if not already done so (events can be
// re-dispatched, so we may be have already done this, so in this case we
// can just update the values). Assignment doesn't work in most browsers.
// Object.defineProperty seems to work, although some browsers
// need the original properties deleted first.
if (!event.hasOwnProperty('srcElement')) {
delete event.srcElement;
Object.defineProperty(event, 'srcElement', {
get: function() { return null; }
});
}
if (event.hasOwnProperty('target')) {
event.target = this;
} else {
delete event.target;
var target = this;
Object.defineProperty(event, 'target', {
get: function() { return target; },
set: function(value) { target = value; }
});
}
if (event.hasOwnProperty('currentTarget')) {
event.currentTarget = null;
} else {
delete event.currentTarget;
var currentTarget = null;
Object.defineProperty(event, 'currentTarget', {
get: function() { return currentTarget; },
set: function(value) { currentTarget = value; }
});
}
return this.recursiveDispatch_(event);
};
/**
* Dispatches an event recursively without changing its original target.
*
* @param {!Event} event
* @return {boolean} True if the default action was prevented.
* @private
*/
shaka.util.FakeEventTarget.prototype.recursiveDispatch_ = function(event) {
event.currentTarget = this;
var list = this.listeners_.get(event.type) || [];
for (var i = 0; i < list.length; ++i) {
var listener = list[i];
try {
if (listener.handleEvent) {
listener.handleEvent(event);
} else {
listener.call(this, event);
}
// NOTE: If needed, stopImmediatePropagation() would be checked here.
} catch (exception) {
// Exceptions during event handlers should not affect the caller,
// but should appear on the console as uncaught, according to MDN:
// http://goo.gl/N6Ff27
shaka.log.error('Uncaught exception in event handler', exception);
}
}
// NOTE: If needed, stopPropagation() would be checked here.
if (this.parent && event.bubbles) {
this.parent.recursiveDispatch_(event);
}
return event.defaultPrevented;
};