blob: 2941502166a4e438aad15902e1b95094da0e0c2c [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)
{
console.assert(typeof eventType === "string", this, eventType, listener, thisObject);
console.assert(typeof listener === "function", this, eventType, listener, thisObject);
console.assert(typeof thisObject === "object" || window.InspectorTest || window.ProtocolTest, this, eventType, listener, thisObject);
thisObject ??= this;
let data = {
listener,
thisObjectWeakRef: new WeakRef(thisObject),
};
WI.Object._listenerThisObjectFinalizationRegistry.register(thisObject, {eventTargetWeakRef: new WeakRef(this), eventType, data}, data);
this._listeners ??= new Multimap;
this._listeners.add(eventType, data);
console.assert(Array.from(this._listeners.get(eventType)).filter((item) => item.listener === listener && item.thisObjectWeakRef.deref() === thisObject).length === 1, this, eventType, listener, thisObject);
return listener;
}
static singleFireEventListener(eventType, listener, thisObject)
{
let eventTargetWeakRef = new WeakRef(this);
return this.addEventListener(eventType, function wrappedCallback() {
eventTargetWeakRef.deref()?.removeEventListener(eventType, wrappedCallback, this);
listener.apply(this, arguments);
}, thisObject);
}
static awaitEvent(eventType, thisObject)
{
return new Promise((resolve, reject) => {
this.singleFireEventListener(eventType, resolve, thisObject);
});
}
static removeEventListener(eventType, listener, thisObject)
{
console.assert(this._listeners, this, eventType, listener, thisObject);
console.assert(typeof eventType === "string", this, eventType, listener, thisObject);
console.assert(typeof listener === "function", this, eventType, listener, thisObject);
console.assert(typeof thisObject === "object" || window.InspectorTest || window.ProtocolTest, this, eventType, listener, thisObject);
if (!this._listeners)
return;
thisObject ??= this;
let listenersForEventType = this._listeners.get(eventType);
console.assert(listenersForEventType, this, eventType, listener, thisObject);
if (!listenersForEventType)
return;
let didDelete = false;
for (let data of listenersForEventType) {
let unwrapped = data.thisObjectWeakRef.deref();
if (!unwrapped || unwrapped !== thisObject || data.listener !== listener)
continue;
if (this._listeners.delete(eventType, data))
didDelete = true;
WI.Object._listenerThisObjectFinalizationRegistry.unregister(data);
}
console.assert(didDelete, this, eventType, listener, thisObject);
}
// Public
addEventListener() { return WI.Object.addEventListener.apply(this, arguments); }
singleFireEventListener() { return WI.Object.singleFireEventListener.apply(this, arguments); }
awaitEvent() { return WI.Object.awaitEvent.apply(this, arguments); }
removeEventListener() { return WI.Object.removeEventListener.apply(this, arguments); }
dispatchEventToListeners(eventType, eventData)
{
let event = new WI.Event(this, eventType, eventData);
function dispatch(object)
{
if (!object || event._stoppedPropagation)
return;
let listeners = object._listeners;
if (!listeners || !object.hasOwnProperty("_listeners") || !listeners.size)
return;
let listenersForEventType = listeners.get(eventType);
if (!listenersForEventType)
return;
// Copy the set of listeners so we don't have to worry about mutating while iterating.
for (let data of Array.from(listenersForEventType)) {
let unwrapped = data.thisObjectWeakRef.deref();
if (!unwrapped)
continue;
data.listener.call(unwrapped, 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;
}
// Test
static hasEventListeners(eventType)
{
console.assert(window.InspectorTest || window.ProtocolTest);
return this._listeners?.has(eventType);
}
static activelyListeningObjectsWithPrototype(proto)
{
console.assert(window.InspectorTest || window.ProtocolTest);
let results = new Set;
if (this._listeners) {
for (let data of this._listeners.values()) {
let unwrapped = data.thisObjectWeakRef.deref();
if (unwrapped instanceof proto)
results.add(unwrapped);
}
}
return results;
}
hasEventListeners() { return WI.Object.hasEventListeners.apply(this, arguments); }
activelyListeningObjectsWithPrototype() { return WI.Object.activelyListeningObjectsWithPrototype.apply(this, arguments); }
};
WI.Object._listenerThisObjectFinalizationRegistry = new FinalizationRegistry((heldValue) => {
heldValue.eventTargetWeakRef.deref()?._listeners.delete(heldValue.eventType, heldValue.data);
});
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", // COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
VisibilityStateDidChange: "visibility-state-did-change",
TransitionPageTarget: "transition-page-target",
};