223 lines
8.1 KiB
JavaScript
223 lines
8.1 KiB
JavaScript
/*
|
|
* 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",
|
|
};
|