a2716403194c758953246591d507f623f778ca69
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Base / Object.js
1 /*
2  * Copyright (C) 2008, 2013 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.Object = class WebInspectorObject
27 {
28     constructor()
29     {
30         this._listeners = null;
31     }
32
33     // Static
34
35     static addEventListener(eventType, listener, thisObject)
36     {
37         thisObject = thisObject || null;
38
39         console.assert(eventType, "Object.addEventListener: invalid event type ", eventType, "(listener: ", listener, "thisObject: ", thisObject, ")");
40         if (!eventType)
41             return;
42
43         console.assert(listener, "Object.addEventListener: invalid listener ", listener, "(event type: ", eventType, "thisObject: ", thisObject, ")");
44         if (!listener)
45             return;
46
47         if (!this._listeners)
48             this._listeners = new Map();
49
50         let listenersTable = this._listeners.get(eventType);
51         if (!listenersTable) {
52             listenersTable = new ListMultimap();
53             this._listeners.set(eventType, listenersTable);
54         }
55
56         listenersTable.add(thisObject, listener);
57     }
58
59     static singleFireEventListener(eventType, listener, thisObject)
60     {
61         let wrappedCallback = () => {
62             this.removeEventListener(eventType, wrappedCallback, null);
63             listener.apply(thisObject, arguments);
64         };
65
66         this.addEventListener(eventType, wrappedCallback, null);
67         return wrappedCallback;
68     }
69
70     static removeEventListener(eventType, listener, thisObject)
71     {
72         eventType = eventType || null;
73         listener = listener || null;
74         thisObject = thisObject || null;
75
76         if (!this._listeners)
77             return;
78
79         if (thisObject && !eventType) {
80             this._listeners.forEach(function(listenersTable) {
81                 let listenerPairs = listenersTable.toArray();
82                 for (let i = 0, length = listenerPairs.length; i < length; ++i) {
83                     let existingThisObject = listenerPairs[i][0];
84                     if (existingThisObject === thisObject)
85                         listenersTable.deleteAll(existingThisObject);
86                 }
87             });
88
89             return;
90         }
91
92         let listenersTable = this._listeners.get(eventType);
93         if (!listenersTable || listenersTable.size === 0)
94             return;
95
96         let didDelete = listenersTable.delete(thisObject, listener);
97         console.assert(didDelete, "removeEventListener cannot remove " + eventType.toString() + " because it doesn't exist.");
98     }
99
100     // Only used by tests.
101     static hasEventListeners(eventType)
102     {
103         if (!this._listeners)
104             return false;
105
106         let listenersTable = this._listeners.get(eventType);
107         return listenersTable && listenersTable.size > 0;
108     }
109
110     // This should only be used within regression tests to detect leaks.
111     static retainedObjectsWithPrototype(proto)
112     {
113         let results = new Set;
114
115         if (this._listeners) {
116             this._listeners.forEach(function(listenersTable, eventType) {
117                 listenersTable.forEach(function(pair) {
118                     let thisObject = pair[0];
119                     if (thisObject instanceof proto)
120                         results.add(thisObject);
121                 });
122             });
123         }
124
125         return results;
126     }
127
128     // Public
129
130     addEventListener() { return WebInspector.Object.addEventListener.apply(this, arguments); }
131     singleFireEventListener() { return WebInspector.Object.singleFireEventListener.apply(this, arguments); }
132     removeEventListener() { return WebInspector.Object.removeEventListener.apply(this, arguments); }
133     hasEventListeners() { return WebInspector.Object.hasEventListeners.apply(this, arguments); }
134     retainedObjectsWithPrototype() { return WebInspector.Object.retainedObjectsWithPrototype.apply(this, arguments); }
135
136     dispatchEventToListeners(eventType, eventData)
137     {
138         let event = new WebInspector.Event(this, eventType, eventData);
139
140         function dispatch(object)
141         {
142             if (!object || !object._listeners || event._stoppedPropagation)
143                 return;
144
145             console.assert(object._listeners instanceof Map);
146
147             let listenersTable = object._listeners.get(eventType);
148             if (!listenersTable)
149                 return;
150
151             // Make a copy with slice so mutations during the loop doesn't affect us.
152             let listeners = listenersTable.toArray();
153
154             // Iterate over the listeners and call them. Stop if stopPropagation is called.
155             for (let i = 0, length = listeners.length; i < length; ++i) {
156                 let [thisObject, listener] = listeners[i];
157                 listener.call(thisObject, event);
158                 if (event._stoppedPropagation)
159                     break;
160             }
161         }
162
163         // Dispatch to listeners of this specific object.
164         dispatch(this);
165
166         // Allow propagation again so listeners on the constructor always have a crack at the event.
167         event._stoppedPropagation = false;
168
169         // Dispatch to listeners on all constructors up the prototype chain, including the immediate constructor.
170         let constructor = this.constructor;
171         while (constructor) {
172             dispatch(constructor);
173
174             if (!constructor.prototype.__proto__)
175                 break;
176
177             constructor = constructor.prototype.__proto__.constructor;
178         }
179
180         return event.defaultPrevented;
181     }
182 };
183
184 WebInspector.Event = class Event
185 {
186     constructor(target, type, data)
187     {
188         this.target = target;
189         this.type = type;
190         this.data = data;
191         this.defaultPrevented = false;
192         this._stoppedPropagation = false;
193     }
194
195     stopPropagation()
196     {
197         this._stoppedPropagation = true;
198     }
199
200     preventDefault()
201     {
202         this.defaultPrevented = true;
203     }
204 };
205
206 WebInspector.notifications = new WebInspector.Object;
207
208 WebInspector.Notification = {
209     GlobalModifierKeysDidChange: "global-modifiers-did-change",
210     PageArchiveStarted: "page-archive-started",
211     PageArchiveEnded: "page-archive-ended",
212     ExtraDomainsActivated: "extra-domains-activated",
213     TabTypesChanged: "tab-types-changed",
214     DebugUIEnabledDidChange: "debug-ui-enabled-did-change",
215 };