blob: 400431309736976da19ec04bcb1dc1c1513074ae [file] [log] [blame]
/*
* Copyright (C) 2008, 2013 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
WI.Object = class WebInspectorObject
{
constructor()
{
this._listeners = null;
}
// Static
static addEventListener(eventType, listener, thisObject)
{
thisObject = thisObject || null;
console.assert(eventType, "Object.addEventListener: invalid event type ", eventType, "(listener: ", listener, "thisObject: ", thisObject, ")");
if (!eventType)
return null;
console.assert(listener, "Object.addEventListener: invalid listener ", listener, "(event type: ", eventType, "thisObject: ", thisObject, ")");
if (!listener)
return null;
if (!this._listeners)
this._listeners = new Map();
let listenersTable = this._listeners.get(eventType);
if (!listenersTable) {
listenersTable = new ListMultimap();
this._listeners.set(eventType, listenersTable);
}
listenersTable.add(thisObject, listener);
return listener;
}
static singleFireEventListener(eventType, listener, thisObject)
{
let wrappedCallback = function() {
this.removeEventListener(eventType, wrappedCallback, null);
listener.apply(thisObject, arguments);
}.bind(this);
this.addEventListener(eventType, wrappedCallback, null);
return wrappedCallback;
}
static removeEventListener(eventType, listener, thisObject)
{
eventType = eventType || null;
listener = listener || null;
thisObject = thisObject || null;
if (!this._listeners)
return;
if (thisObject && !eventType) {
this._listeners.forEach(function(listenersTable) {
listenersTable.deleteAll(thisObject);
});
return;
}
let listenersTable = this._listeners.get(eventType);
if (!listenersTable || listenersTable.size === 0)
return;
let didDelete = listenersTable.delete(thisObject, listener);
console.assert(didDelete, "removeEventListener cannot remove " + eventType.toString() + " because it doesn't exist.");
}
static awaitEvent(eventType)
{
let wrapper = new WI.WrappedPromise;
this.singleFireEventListener(eventType, (event) => wrapper.resolve(event));
return wrapper.promise;
}
// Only used by tests.
static hasEventListeners(eventType)
{
if (!this._listeners)
return false;
let listenersTable = this._listeners.get(eventType);
return listenersTable && listenersTable.size > 0;
}
// This should only be used within regression tests to detect leaks.
static retainedObjectsWithPrototype(proto)
{
let results = new Set;
if (this._listeners) {
this._listeners.forEach(function(listenersTable, eventType) {
listenersTable.forEach(function(pair) {
let thisObject = pair[0];
if (thisObject instanceof proto)
results.add(thisObject);
});
});
}
return results;
}
// Public
addEventListener() { return WI.Object.addEventListener.apply(this, arguments); }
singleFireEventListener() { return WI.Object.singleFireEventListener.apply(this, arguments); }
removeEventListener() { return WI.Object.removeEventListener.apply(this, arguments); }
awaitEvent() { return WI.Object.awaitEvent.apply(this, arguments); }
hasEventListeners() { return WI.Object.hasEventListeners.apply(this, arguments); }
retainedObjectsWithPrototype() { return WI.Object.retainedObjectsWithPrototype.apply(this, arguments); }
dispatchEventToListeners(eventType, eventData)
{
let event = new WI.Event(this, eventType, eventData);
function dispatch(object)
{
if (!object || event._stoppedPropagation)
return;
let listenerTypesMap = object._listeners;
if (!listenerTypesMap || !object.hasOwnProperty("_listeners"))
return;
console.assert(listenerTypesMap instanceof Map);
let listenersTable = listenerTypesMap.get(eventType);
if (!listenersTable)
return;
// Make a copy with slice so mutations during the loop doesn't affect us.
let listeners = listenersTable.toArray();
// Iterate over the listeners and call them. Stop if stopPropagation is called.
for (let i = 0, length = listeners.length; i < length; ++i) {
let [thisObject, listener] = listeners[i];
listener.call(thisObject, event);
if (event._stoppedPropagation)
break;
}
}
// Dispatch to listeners of this specific object.
dispatch(this);
// Allow propagation again so listeners on the constructor always have a crack at the event.
event._stoppedPropagation = false;
// Dispatch to listeners on all constructors up the prototype chain, including the immediate constructor.
let constructor = this.constructor;
while (constructor) {
dispatch(constructor);
if (!constructor.prototype.__proto__)
break;
constructor = constructor.prototype.__proto__.constructor;
}
return event.defaultPrevented;
}
};
WI.Event = class Event
{
constructor(target, type, data)
{
this.target = target;
this.type = type;
this.data = data;
this.defaultPrevented = false;
this._stoppedPropagation = false;
}
stopPropagation()
{
this._stoppedPropagation = true;
}
preventDefault()
{
this.defaultPrevented = true;
}
};
WI.notifications = new WI.Object;
WI.Notification = {
GlobalModifierKeysDidChange: "global-modifiers-did-change",
PageArchiveStarted: "page-archive-started",
PageArchiveEnded: "page-archive-ended",
ExtraDomainsActivated: "extra-domains-activated",
VisibilityStateDidChange: "visibility-state-did-change",
TransitionPageTarget: "transition-page-target",
};