/* * 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", };