REGRESSION (r195305): Web Inspector: WebInspector.Object can dispatch constructor...
[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 || event._stoppedPropagation)
143                 return;
144
145             let listenerTypesMap = object._listeners;
146             if (!listenerTypesMap || !object.hasOwnProperty("_listeners"))
147                 return;
148
149             console.assert(listenerTypesMap instanceof Map);
150
151             let listenersTable = listenerTypesMap.get(eventType);
152             if (!listenersTable)
153                 return;
154
155             // Make a copy with slice so mutations during the loop doesn't affect us.
156             let listeners = listenersTable.toArray();
157
158             // Iterate over the listeners and call them. Stop if stopPropagation is called.
159             for (let i = 0, length = listeners.length; i < length; ++i) {
160                 let [thisObject, listener] = listeners[i];
161                 listener.call(thisObject, event);
162                 if (event._stoppedPropagation)
163                     break;
164             }
165         }
166
167         // Dispatch to listeners of this specific object.
168         dispatch(this);
169
170         // Allow propagation again so listeners on the constructor always have a crack at the event.
171         event._stoppedPropagation = false;
172
173         // Dispatch to listeners on all constructors up the prototype chain, including the immediate constructor.
174         let constructor = this.constructor;
175         while (constructor) {
176             dispatch(constructor);
177
178             if (!constructor.prototype.__proto__)
179                 break;
180
181             constructor = constructor.prototype.__proto__.constructor;
182         }
183
184         return event.defaultPrevented;
185     }
186 };
187
188 WebInspector.Event = class Event
189 {
190     constructor(target, type, data)
191     {
192         this.target = target;
193         this.type = type;
194         this.data = data;
195         this.defaultPrevented = false;
196         this._stoppedPropagation = false;
197     }
198
199     stopPropagation()
200     {
201         this._stoppedPropagation = true;
202     }
203
204     preventDefault()
205     {
206         this.defaultPrevented = true;
207     }
208 };
209
210 WebInspector.notifications = new WebInspector.Object;
211
212 WebInspector.Notification = {
213     GlobalModifierKeysDidChange: "global-modifiers-did-change",
214     PageArchiveStarted: "page-archive-started",
215     PageArchiveEnded: "page-archive-ended",
216     ExtraDomainsActivated: "extra-domains-activated",
217     TabTypesChanged: "tab-types-changed",
218     DebugUIEnabledDidChange: "debug-ui-enabled-did-change",
219 };