OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 var eventBindingsNatives = requireNative('event_bindings'); | 5 var eventBindingsNatives = requireNative('event_bindings'); |
6 var AttachEvent = eventBindingsNatives.AttachEvent; | 6 var AttachEvent = eventBindingsNatives.AttachEvent; |
7 var DetachEvent = eventBindingsNatives.DetachEvent; | 7 var DetachEvent = eventBindingsNatives.DetachEvent; |
8 var AttachFilteredEvent = eventBindingsNatives.AttachFilteredEvent; | |
9 var DetachFilteredEvent = eventBindingsNatives.DetachFilteredEvent; | |
Matt Perry
2012/05/15 19:28:27
I don't see these in this CL.
koz (OOO until 15th September)
2012/05/16 01:26:12
These are only exercised by the unit test where we
| |
8 | 10 |
9 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); | 11 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); |
10 | 12 |
11 // Local implementation of JSON.parse & JSON.stringify that protect us | 13 // Local implementation of JSON.parse & JSON.stringify that protect us |
12 // from being clobbered by an extension. | 14 // from being clobbered by an extension. |
13 // | 15 // |
14 // TODO(aa): This makes me so sad. We shouldn't need it, as we can just pass | 16 // TODO(aa): This makes me so sad. We shouldn't need it, as we can just pass |
15 // Values directly over IPC without serializing to strings and use | 17 // Values directly over IPC without serializing to strings and use |
16 // JSONValueConverter. | 18 // JSONValueConverter. |
17 chromeHidden.JSON = new (function() { | 19 chromeHidden.JSON = new (function() { |
(...skipping 21 matching lines...) Expand all Loading... | |
39 $Array.prototype.toJSON = customizedArrayToJSON; | 41 $Array.prototype.toJSON = customizedArrayToJSON; |
40 } | 42 } |
41 } | 43 } |
42 }; | 44 }; |
43 | 45 |
44 this.parse = function(thing) { | 46 this.parse = function(thing) { |
45 return $jsonParse(thing); | 47 return $jsonParse(thing); |
46 }; | 48 }; |
47 })(); | 49 })(); |
48 | 50 |
51 // Handles adding/removing/dispatching listeners for unfiltered events. | |
52 var UnfilteredAttachmentStrategy = function(event) { | |
53 this.event_ = event; | |
54 this.eventName_ = event.eventName_; | |
55 }; | |
56 | |
57 UnfilteredAttachmentStrategy.prototype.onAddedListener = | |
58 function(listener) { | |
59 // Only attach / detach on the first / last listener removed. | |
Matt Perry
2012/05/15 19:28:27
Is this proper formatting? I thought indent should
koz (OOO until 15th September)
2012/05/16 01:26:12
Done.
| |
60 if (this.event_.listeners_.length > 1) | |
61 return; | |
62 AttachEvent(this.eventName_); | |
63 allAttachedEvents[allAttachedEvents.length] = this.event_; | |
64 if (!this.eventName_) | |
65 return; | |
66 | |
67 if (attachedNamedEvents[this.eventName_]) { | |
68 throw new Error("chrome.Event '" + this.eventName_ + | |
69 "' is already attached."); | |
70 } | |
71 | |
72 attachedNamedEvents[this.eventName_] = this.event_; | |
73 }; | |
74 | |
75 UnfilteredAttachmentStrategy.prototype.onRemovedListener = | |
76 function(listener) { | |
77 if (this.event_.listeners_.length == 0) | |
78 this.detach(true); | |
79 }; | |
80 | |
81 UnfilteredAttachmentStrategy.prototype.detach = | |
82 function(manual) { | |
83 var i = allAttachedEvents.indexOf(this.event_); | |
84 if (i >= 0) | |
85 delete allAttachedEvents[i]; | |
86 DetachEvent(this.eventName_, manual); | |
87 if (!this.eventName_) | |
88 return; | |
89 | |
90 if (!attachedNamedEvents[this.eventName_]) { | |
91 throw new Error("chrome.Event '" + this.eventName_ + | |
92 "' is not attached."); | |
93 } | |
94 | |
95 delete attachedNamedEvents[this.eventName_]; | |
96 }; | |
97 | |
98 var FilteredAttachmentStrategy = function(event) { | |
99 this.event_ = event; | |
100 this.eventName_ = event.eventName_; | |
101 this.listenerMap_ = {}; | |
102 }; | |
103 | |
104 FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) { | |
105 var id = AttachFilteredEvent(this.eventName_, listener.filters || {}); | |
106 listener.id = id; | |
107 this.listenerMap_[id] = listener; | |
108 }; | |
109 | |
110 FilteredAttachmentStrategy.prototype.onRemovedListener = function(listener) { | |
111 this.detachListener(listener, true); | |
112 } | |
113 | |
114 FilteredAttachmentStrategy.prototype.detachListener = | |
115 function(listener, manual) { | |
116 if (listener.id == undefined) | |
117 throw new Error("listener.id undefined - '" + listener + "'"); | |
118 var id = listener.id; | |
119 delete this.listenerMap_[id]; | |
120 DetachFilteredEvent(id, manual); | |
121 }; | |
122 | |
123 FilteredAttachmentStrategy.prototype.detach = function(manual) { | |
124 for (var i in this.listenerMap_) | |
125 this.detachListener(this.listenerMap_[i], manual); | |
126 }; | |
127 | |
49 // Event object. If opt_eventName is provided, this object represents | 128 // Event object. If opt_eventName is provided, this object represents |
50 // the unique instance of that named event, and dispatching an event | 129 // the unique instance of that named event, and dispatching an event |
51 // with that name will route through this object's listeners. Note that | 130 // with that name will route through this object's listeners. Note that |
52 // opt_eventName is required for events that support rules. | 131 // opt_eventName is required for events that support rules. |
53 // | 132 // |
54 // Example: | 133 // Example: |
55 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); | 134 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); |
56 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); | 135 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); |
57 // chromeHidden.Event.dispatch("tab-changed", "hi"); | 136 // chromeHidden.Event.dispatch("tab-changed", "hi"); |
58 // will result in an alert dialog that says 'hi'. | 137 // will result in an alert dialog that says 'hi'. |
59 // | 138 // |
60 // If opt_eventOptions exists, it is a dictionary that contains the boolean | 139 // If opt_eventOptions exists, it is a dictionary that contains the boolean |
61 // entries "supportsListeners" and "supportsRules". | 140 // entries "supportsListeners" and "supportsRules". |
62 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { | 141 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { |
63 this.eventName_ = opt_eventName; | 142 this.eventName_ = opt_eventName; |
64 this.listeners_ = []; | 143 this.listeners_ = []; |
65 this.eventOptions_ = opt_eventOptions || | 144 this.eventOptions_ = opt_eventOptions || |
66 {"supportsListeners": true, "supportsRules": false}; | 145 {"supportsListeners": true, |
146 "supportsRules": false, | |
147 "supportsFilters": false, | |
Matt Perry
2012/05/15 19:28:27
nit: quotes are unnecessary
koz (OOO until 15th September)
2012/05/16 01:26:12
Done.
| |
148 }; | |
67 | 149 |
68 if (this.eventOptions_.supportsRules && !opt_eventName) | 150 if (this.eventOptions_.supportsRules && !opt_eventName) |
69 throw new Error("Events that support rules require an event name."); | 151 throw new Error("Events that support rules require an event name."); |
70 | 152 |
153 if (this.eventOptions_.supportsFilters) { | |
154 this.attachmentStrategy_ = new FilteredAttachmentStrategy(this); | |
155 } else { | |
156 this.attachmentStrategy_ = new UnfilteredAttachmentStrategy(this); | |
157 } | |
158 | |
71 // Validate event parameters if we are in debug. | 159 // Validate event parameters if we are in debug. |
72 if (opt_argSchemas && | 160 if (opt_argSchemas && |
73 chromeHidden.validateCallbacks && | 161 chromeHidden.validateCallbacks && |
74 chromeHidden.validate) { | 162 chromeHidden.validate) { |
75 | 163 |
76 this.validate_ = function(args) { | 164 this.validate_ = function(args) { |
77 try { | 165 try { |
78 chromeHidden.validate(args, opt_argSchemas); | 166 chromeHidden.validate(args, opt_argSchemas); |
79 } catch (exception) { | 167 } catch (exception) { |
80 return "Event validation error during " + opt_eventName + " -- " + | 168 return "Event validation error during " + opt_eventName + " -- " + |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
129 } | 217 } |
130 }; | 218 }; |
131 | 219 |
132 // Test if a named event has any listeners. | 220 // Test if a named event has any listeners. |
133 chromeHidden.Event.hasListener = function(name) { | 221 chromeHidden.Event.hasListener = function(name) { |
134 return (attachedNamedEvents[name] && | 222 return (attachedNamedEvents[name] && |
135 attachedNamedEvents[name].listeners_.length > 0); | 223 attachedNamedEvents[name].listeners_.length > 0); |
136 }; | 224 }; |
137 | 225 |
138 // Registers a callback to be called when this event is dispatched. | 226 // Registers a callback to be called when this event is dispatched. |
139 chrome.Event.prototype.addListener = function(cb) { | 227 chrome.Event.prototype.addListener = function(cb, filters) { |
140 if (!this.eventOptions_.supportsListeners) | 228 if (!this.eventOptions_.supportsListeners) |
141 throw new Error("This event does not support listeners."); | 229 throw new Error("This event does not support listeners."); |
142 if (this.listeners_.length == 0) { | 230 if (!this.eventOptions_.supportsFilters && filters) |
143 this.attach_(); | 231 throw new Error("This event does not support filters."); |
144 } | 232 var listener = {callback: cb, filters: filters}; |
145 this.listeners_.push(cb); | 233 this.listeners_.push(listener); |
234 this.attachmentStrategy_.onAddedListener(listener); | |
146 }; | 235 }; |
147 | 236 |
148 // Unregisters a callback. | 237 // Unregisters a callback. |
149 chrome.Event.prototype.removeListener = function(cb) { | 238 chrome.Event.prototype.removeListener = function(cb) { |
150 if (!this.eventOptions_.supportsListeners) | 239 if (!this.eventOptions_.supportsListeners) |
151 throw new Error("This event does not support listeners."); | 240 throw new Error("This event does not support listeners."); |
152 var idx = this.findListener_(cb); | 241 var idx = this.findListener_(cb); |
153 if (idx == -1) { | 242 if (idx == -1) { |
154 return; | 243 return; |
155 } | 244 } |
156 | 245 |
246 var removedListener = this.listeners_[idx]; | |
157 this.listeners_.splice(idx, 1); | 247 this.listeners_.splice(idx, 1); |
158 if (this.listeners_.length == 0) { | 248 this.attachmentStrategy_.onRemovedListener(removedListener); |
159 this.detach_(true); | |
160 } | |
161 }; | 249 }; |
162 | 250 |
163 // Test if the given callback is registered for this event. | 251 // Test if the given callback is registered for this event. |
164 chrome.Event.prototype.hasListener = function(cb) { | 252 chrome.Event.prototype.hasListener = function(cb) { |
165 if (!this.eventOptions_.supportsListeners) | 253 if (!this.eventOptions_.supportsListeners) |
166 throw new Error("This event does not support listeners."); | 254 throw new Error("This event does not support listeners."); |
167 return this.findListener_(cb) > -1; | 255 return this.findListener_(cb) > -1; |
168 }; | 256 }; |
169 | 257 |
170 // Test if any callbacks are registered for this event. | 258 // Test if any callbacks are registered for this event. |
171 chrome.Event.prototype.hasListeners = function() { | 259 chrome.Event.prototype.hasListeners = function() { |
172 if (!this.eventOptions_.supportsListeners) | 260 if (!this.eventOptions_.supportsListeners) |
173 throw new Error("This event does not support listeners."); | 261 throw new Error("This event does not support listeners."); |
174 return this.listeners_.length > 0; | 262 return this.listeners_.length > 0; |
175 }; | 263 }; |
176 | 264 |
177 // Returns the index of the given callback if registered, or -1 if not | 265 // Returns the index of the given callback if registered, or -1 if not |
178 // found. | 266 // found. |
179 chrome.Event.prototype.findListener_ = function(cb) { | 267 chrome.Event.prototype.findListener_ = function(cb) { |
180 for (var i = 0; i < this.listeners_.length; i++) { | 268 for (var i = 0; i < this.listeners_.length; i++) { |
181 if (this.listeners_[i] == cb) { | 269 if (this.listeners_[i].callback == cb) { |
182 return i; | 270 return i; |
183 } | 271 } |
184 } | 272 } |
185 | 273 |
186 return -1; | 274 return -1; |
187 }; | 275 }; |
188 | 276 |
189 // Dispatches this event object to all listeners, passing all supplied | 277 // Dispatches this event object to all listeners, passing all supplied |
190 // arguments to this function each listener. | 278 // arguments to this function each listener. |
191 chrome.Event.prototype.dispatch = function(varargs) { | 279 chrome.Event.prototype.dispatch = function(varargs) { |
192 if (!this.eventOptions_.supportsListeners) | 280 if (!this.eventOptions_.supportsListeners) |
193 throw new Error("This event does not support listeners."); | 281 throw new Error("This event does not support listeners."); |
194 var args = Array.prototype.slice.call(arguments); | 282 var args = Array.prototype.slice.call(arguments); |
195 var validationErrors = this.validate_(args); | 283 var validationErrors = this.validate_(args); |
196 if (validationErrors) { | 284 if (validationErrors) { |
197 return {validationErrors: validationErrors}; | 285 return {validationErrors: validationErrors}; |
198 } | 286 } |
199 var results = []; | 287 var results = []; |
200 for (var i = 0; i < this.listeners_.length; i++) { | 288 for (var i = 0; i < this.listeners_.length; i++) { |
201 try { | 289 try { |
202 var result = this.listeners_[i].apply(null, args); | 290 var result = this.listeners_[i].callback.apply(null, args); |
203 if (result !== undefined) | 291 if (result !== undefined) |
204 results.push(result); | 292 results.push(result); |
205 } catch (e) { | 293 } catch (e) { |
206 console.error("Error in event handler for '" + this.eventName_ + | 294 console.error("Error in event handler for '" + this.eventName_ + |
207 "': " + e.message + ' ' + e.stack); | 295 "': " + e.message + ' ' + e.stack); |
208 } | 296 } |
209 } | 297 } |
210 if (results.length) | 298 if (results.length) |
211 return {results: results}; | 299 return {results: results}; |
212 }; | 300 }; |
213 | 301 |
214 // Attaches this event object to its name. Only one object can have a given | |
215 // name. | |
216 chrome.Event.prototype.attach_ = function() { | |
217 AttachEvent(this.eventName_); | |
218 allAttachedEvents[allAttachedEvents.length] = this; | |
219 if (!this.eventName_) | |
220 return; | |
221 | |
222 if (attachedNamedEvents[this.eventName_]) { | |
223 throw new Error("chrome.Event '" + this.eventName_ + | |
224 "' is already attached."); | |
225 } | |
226 | |
227 attachedNamedEvents[this.eventName_] = this; | |
228 }; | |
229 | |
230 // Detaches this event object from its name. | 302 // Detaches this event object from its name. |
231 chrome.Event.prototype.detach_ = function(manual) { | 303 chrome.Event.prototype.detach = function() { |
232 var i = allAttachedEvents.indexOf(this); | 304 this.attachmentStrategy_.detach(false); |
233 if (i >= 0) | |
234 delete allAttachedEvents[i]; | |
235 DetachEvent(this.eventName_, manual); | |
236 if (!this.eventName_) | |
237 return; | |
238 | |
239 if (!attachedNamedEvents[this.eventName_]) { | |
240 throw new Error("chrome.Event '" + this.eventName_ + | |
241 "' is not attached."); | |
242 } | |
243 | |
244 delete attachedNamedEvents[this.eventName_]; | |
245 }; | 305 }; |
246 | 306 |
247 chrome.Event.prototype.destroy_ = function() { | 307 chrome.Event.prototype.destroy_ = function() { |
248 this.listeners_ = []; | 308 var listenersToRemove = this.listeners_.slice(); |
309 for (var listener in listenersToRemove) { | |
310 this.removeListener(listener); | |
Matt Perry
2012/05/15 19:28:27
This will result in detach being called with manua
koz (OOO until 15th September)
2012/05/16 01:26:12
Indeed that is what happens and this is dead code.
| |
311 } | |
249 this.validate_ = []; | 312 this.validate_ = []; |
250 this.detach_(false); | |
251 }; | 313 }; |
252 | 314 |
253 // Gets the declarative API object, or undefined if this extension doesn't | 315 // Gets the declarative API object, or undefined if this extension doesn't |
254 // have access to it. | 316 // have access to it. |
255 // | 317 // |
256 // This is defined as a function (rather than a variable) because it isn't | 318 // This is defined as a function (rather than a variable) because it isn't |
257 // accessible until the schema bindings have been generated. | 319 // accessible until the schema bindings have been generated. |
258 function getDeclarativeAPI() { | 320 function getDeclarativeAPI() { |
259 return chromeHidden.internalAPIs.declarative; | 321 return chromeHidden.internalAPIs.declarative; |
260 } | 322 } |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
298 chromeHidden.onUnload = new chrome.Event(); | 360 chromeHidden.onUnload = new chrome.Event(); |
299 | 361 |
300 chromeHidden.dispatchOnLoad = | 362 chromeHidden.dispatchOnLoad = |
301 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); | 363 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); |
302 | 364 |
303 chromeHidden.dispatchOnUnload = function() { | 365 chromeHidden.dispatchOnUnload = function() { |
304 chromeHidden.onUnload.dispatch(); | 366 chromeHidden.onUnload.dispatch(); |
305 for (var i = 0; i < allAttachedEvents.length; ++i) { | 367 for (var i = 0; i < allAttachedEvents.length; ++i) { |
306 var event = allAttachedEvents[i]; | 368 var event = allAttachedEvents[i]; |
307 if (event) | 369 if (event) |
308 event.detach_(false); | 370 event.detach(); |
309 } | 371 } |
310 }; | 372 }; |
311 | 373 |
312 chromeHidden.dispatchError = function(msg) { | 374 chromeHidden.dispatchError = function(msg) { |
313 console.error(msg); | 375 console.error(msg); |
314 }; | 376 }; |
315 | 377 |
316 exports.Event = chrome.Event; | 378 exports.Event = chrome.Event; |
OLD | NEW |