Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(109)

Side by Side Diff: chrome/renderer/resources/extensions/event.js

Issue 10661038: Revert 143896 - Filtered events. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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;
10 var MatchAgainstEventFilter = eventBindingsNatives.MatchAgainstEventFilter;
11 var sendRequest = require('sendRequest').sendRequest; 8 var sendRequest = require('sendRequest').sendRequest;
12 var utils = require('utils'); 9 var utils = require('utils');
13 var validate = require('schemaUtils').validate; 10 var validate = require('schemaUtils').validate;
14 11
15 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); 12 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden();
16 var GetExtensionAPIDefinition = 13 var GetExtensionAPIDefinition =
17 requireNative('apiDefinitions').GetExtensionAPIDefinition; 14 requireNative('apiDefinitions').GetExtensionAPIDefinition;
18 15
19 // Schemas for the rule-style functions on the events API that 16 // Schemas for the rule-style functions on the events API that
20 // only need to be generated occasionally, so populate them lazily. 17 // only need to be generated occasionally, so populate them lazily.
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
71 $Array.prototype.toJSON = customizedArrayToJSON; 68 $Array.prototype.toJSON = customizedArrayToJSON;
72 } 69 }
73 } 70 }
74 }; 71 };
75 72
76 this.parse = function(thing) { 73 this.parse = function(thing) {
77 return $jsonParse(thing); 74 return $jsonParse(thing);
78 }; 75 };
79 })(); 76 })();
80 77
81 // A map of event names to the event object that is registered to that name.
82 var attachedNamedEvents = {};
83
84 // An array of all attached event objects, used for detaching on unload.
85 var allAttachedEvents = [];
86
87 // A map of functions that massage event arguments before they are dispatched.
88 // Key is event name, value is function.
89 var eventArgumentMassagers = {};
90
91 // Handles adding/removing/dispatching listeners for unfiltered events.
92 var UnfilteredAttachmentStrategy = function(event) {
93 this.event_ = event;
94 };
95
96 UnfilteredAttachmentStrategy.prototype.onAddedListener =
97 function(listener) {
98 // Only attach / detach on the first / last listener removed.
99 if (this.event_.listeners_.length == 0)
100 AttachEvent(this.event_.eventName_);
101 };
102
103 UnfilteredAttachmentStrategy.prototype.onRemovedListener =
104 function(listener) {
105 if (this.event_.listeners_.length == 0)
106 this.detach(true);
107 };
108
109 UnfilteredAttachmentStrategy.prototype.detach = function(manual) {
110 DetachEvent(this.event_.eventName_, manual);
111 };
112
113 UnfilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
114 return this.event_.listeners_;
115 };
116
117 var FilteredAttachmentStrategy = function(event) {
118 this.event_ = event;
119 this.listenerMap_ = {};
120 };
121
122 FilteredAttachmentStrategy.idToEventMap = {};
123
124 FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) {
125 var id = AttachFilteredEvent(this.event_.eventName_,
126 listener.filters || {});
127 if (id == -1)
128 throw new Error("Can't add listener");
129 listener.id = id;
130 this.listenerMap_[id] = listener;
131 FilteredAttachmentStrategy.idToEventMap[id] = this.event_;
132 };
133
134 FilteredAttachmentStrategy.prototype.onRemovedListener = function(listener) {
135 this.detachListener(listener, true);
136 };
137
138 FilteredAttachmentStrategy.prototype.detachListener =
139 function(listener, manual) {
140 if (listener.id == undefined)
141 throw new Error("listener.id undefined - '" + listener + "'");
142 var id = listener.id;
143 delete this.listenerMap_[id];
144 delete FilteredAttachmentStrategy.idToEventMap[id];
145 DetachFilteredEvent(id, manual);
146 };
147
148 FilteredAttachmentStrategy.prototype.detach = function(manual) {
149 for (var i in this.listenerMap_)
150 this.detachListener(this.listenerMap_[i], manual);
151 };
152
153 FilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
154 var result = [];
155 for (var i = 0; i < ids.length; i++)
156 result.push(this.listenerMap_[ids[i]]);
157 return result;
158 };
159
160 // Event object. If opt_eventName is provided, this object represents 78 // Event object. If opt_eventName is provided, this object represents
161 // the unique instance of that named event, and dispatching an event 79 // the unique instance of that named event, and dispatching an event
162 // with that name will route through this object's listeners. Note that 80 // with that name will route through this object's listeners. Note that
163 // opt_eventName is required for events that support rules. 81 // opt_eventName is required for events that support rules.
164 // 82 //
165 // Example: 83 // Example:
166 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); 84 // chrome.tabs.onChanged = new chrome.Event("tab-changed");
167 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); 85 // chrome.tabs.onChanged.addListener(function(data) { alert(data); });
168 // chromeHidden.Event.dispatch("tab-changed", "hi"); 86 // chromeHidden.Event.dispatch("tab-changed", "hi");
169 // will result in an alert dialog that says 'hi'. 87 // will result in an alert dialog that says 'hi'.
170 // 88 //
171 // If opt_eventOptions exists, it is a dictionary that contains the boolean 89 // If opt_eventOptions exists, it is a dictionary that contains the boolean
172 // entries "supportsListeners" and "supportsRules". 90 // entries "supportsListeners" and "supportsRules".
173 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { 91 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) {
174 this.eventName_ = opt_eventName; 92 this.eventName_ = opt_eventName;
175 this.listeners_ = []; 93 this.listeners_ = [];
176 this.eventOptions_ = opt_eventOptions || 94 this.eventOptions_ = opt_eventOptions ||
177 {supportsFilters: false, 95 {"supportsListeners": true, "supportsRules": false};
178 supportsListeners: true,
179 supportsRules: false,
180 };
181 96
182 if (this.eventOptions_.supportsRules && !opt_eventName) 97 if (this.eventOptions_.supportsRules && !opt_eventName)
183 throw new Error("Events that support rules require an event name."); 98 throw new Error("Events that support rules require an event name.");
184 99
185 if (this.eventOptions_.supportsFilters) {
186 this.attachmentStrategy_ = new FilteredAttachmentStrategy(this);
187 } else {
188 this.attachmentStrategy_ = new UnfilteredAttachmentStrategy(this);
189 }
190
191 // Validate event arguments (the data that is passed to the callbacks) 100 // Validate event arguments (the data that is passed to the callbacks)
192 // if we are in debug. 101 // if we are in debug.
193 if (opt_argSchemas && 102 if (opt_argSchemas &&
194 chromeHidden.validateCallbacks) { 103 chromeHidden.validateCallbacks) {
195 104
196 this.validateEventArgs_ = function(args) { 105 this.validateEventArgs_ = function(args) {
197 try { 106 try {
198 validate(args, opt_argSchemas); 107 validate(args, opt_argSchemas);
199 } catch (exception) { 108 } catch (exception) {
200 return "Event validation error during " + opt_eventName + " -- " + 109 return "Event validation error during " + opt_eventName + " -- " +
201 exception; 110 exception;
202 } 111 }
203 }; 112 };
204 } else { 113 } else {
205 this.validateEventArgs_ = function() {} 114 this.validateEventArgs_ = function() {}
206 } 115 }
207 }; 116 };
208 117
118 // A map of event names to the event object that is registered to that name.
119 var attachedNamedEvents = {};
120
121 // An array of all attached event objects, used for detaching on unload.
122 var allAttachedEvents = [];
123
124 // A map of functions that massage event arguments before they are dispatched.
125 // Key is event name, value is function.
126 var eventArgumentMassagers = {};
127
209 chromeHidden.Event = {}; 128 chromeHidden.Event = {};
210 129
211 chromeHidden.Event.registerArgumentMassager = function(name, fn) { 130 chromeHidden.Event.registerArgumentMassager = function(name, fn) {
212 if (eventArgumentMassagers[name]) 131 if (eventArgumentMassagers[name])
213 throw new Error("Massager already registered for event: " + name); 132 throw new Error("Massager already registered for event: " + name);
214 eventArgumentMassagers[name] = fn; 133 eventArgumentMassagers[name] = fn;
215 }; 134 };
216 135
217 // Dispatches a named event with the given JSON array, which is deserialized 136 // Dispatches a named event with the given JSON array, which is deserialized
218 // before dispatch. The JSON array is the list of arguments that will be 137 // before dispatch. The JSON array is the list of arguments that will be
219 // sent with the event callback. 138 // sent with the event callback.
220 chromeHidden.Event.dispatchJSON = function(name, args, filteringInfo) { 139 chromeHidden.Event.dispatchJSON = function(name, args) {
221 var listenerIDs = null;
222
223 if (filteringInfo) {
224 listenerIDs = MatchAgainstEventFilter(name, filteringInfo);
225 }
226 if (attachedNamedEvents[name]) { 140 if (attachedNamedEvents[name]) {
227 if (args) { 141 if (args) {
228 // TODO(asargent): This is an antiquity. Until all callers of 142 // TODO(asargent): This is an antiquity. Until all callers of
229 // dispatchJSON use actual values, this must remain here to catch the 143 // dispatchJSON use actual values, this must remain here to catch the
230 // cases where a caller has hard-coded a JSON string to pass in. 144 // cases where a caller has hard-coded a JSON string to pass in.
231 if (typeof(args) == "string") { 145 if (typeof(args) == "string") {
232 args = chromeHidden.JSON.parse(args); 146 args = chromeHidden.JSON.parse(args);
233 } 147 }
234 if (eventArgumentMassagers[name]) 148 if (eventArgumentMassagers[name])
235 eventArgumentMassagers[name](args); 149 eventArgumentMassagers[name](args);
236 } 150 }
237 151 var result = attachedNamedEvents[name].dispatch.apply(
238 var event = attachedNamedEvents[name]; 152 attachedNamedEvents[name], args);
239 var result;
240 // TODO(koz): We have to do this differently for unfiltered events (which
241 // have listenerIDs = null) because some bindings write over
242 // event.dispatch (eg: experimental.app.custom_bindings.js) and so expect
243 // events to go through it. These places need to be fixed so that they
244 // expect a listenerIDs parameter.
245 if (listenerIDs)
246 result = event.dispatch_(args, listenerIDs);
247 else
248 result = event.dispatch.apply(event, args);
249 if (result && result.validationErrors) 153 if (result && result.validationErrors)
250 return result.validationErrors; 154 return result.validationErrors;
251 } 155 }
252 }; 156 };
253 157
254 // Dispatches a named event with the given arguments, supplied as an array. 158 // Dispatches a named event with the given arguments, supplied as an array.
255 chromeHidden.Event.dispatch = function(name, args) { 159 chromeHidden.Event.dispatch = function(name, args) {
256 if (attachedNamedEvents[name]) { 160 if (attachedNamedEvents[name]) {
257 attachedNamedEvents[name].dispatch.apply( 161 attachedNamedEvents[name].dispatch.apply(
258 attachedNamedEvents[name], args); 162 attachedNamedEvents[name], args);
259 } 163 }
260 }; 164 };
261 165
262 // Test if a named event has any listeners. 166 // Test if a named event has any listeners.
263 chromeHidden.Event.hasListener = function(name) { 167 chromeHidden.Event.hasListener = function(name) {
264 return (attachedNamedEvents[name] && 168 return (attachedNamedEvents[name] &&
265 attachedNamedEvents[name].listeners_.length > 0); 169 attachedNamedEvents[name].listeners_.length > 0);
266 }; 170 };
267 171
268 // Registers a callback to be called when this event is dispatched. 172 // Registers a callback to be called when this event is dispatched.
269 chrome.Event.prototype.addListener = function(cb, filters) { 173 chrome.Event.prototype.addListener = function(cb) {
270 if (!this.eventOptions_.supportsListeners) 174 if (!this.eventOptions_.supportsListeners)
271 throw new Error("This event does not support listeners."); 175 throw new Error("This event does not support listeners.");
272 if (filters) { 176 if (this.listeners_.length == 0) {
273 if (!this.eventOptions_.supportsFilters) 177 this.attach_();
274 throw new Error("This event does not support filters.");
275 if (filters.url && !(filters.url instanceof Array))
276 throw new Error("filters.url should be an array");
277 } 178 }
278 var listener = {callback: cb, filters: filters}; 179 this.listeners_.push(cb);
279 this.attach_(listener);
280 this.listeners_.push(listener);
281 };
282
283 chrome.Event.prototype.attach_ = function(listener) {
284 this.attachmentStrategy_.onAddedListener(listener);
285 if (this.listeners_.length == 0) {
286 allAttachedEvents[allAttachedEvents.length] = this;
287 if (!this.eventName_)
288 return;
289
290 if (attachedNamedEvents[this.eventName_]) {
291 throw new Error("chrome.Event '" + this.eventName_ +
292 "' is already attached.");
293 }
294
295 attachedNamedEvents[this.eventName_] = this;
296 }
297 }; 180 };
298 181
299 // Unregisters a callback. 182 // Unregisters a callback.
300 chrome.Event.prototype.removeListener = function(cb) { 183 chrome.Event.prototype.removeListener = function(cb) {
301 if (!this.eventOptions_.supportsListeners) 184 if (!this.eventOptions_.supportsListeners)
302 throw new Error("This event does not support listeners."); 185 throw new Error("This event does not support listeners.");
303 var idx = this.findListener_(cb); 186 var idx = this.findListener_(cb);
304 if (idx == -1) { 187 if (idx == -1) {
305 return; 188 return;
306 } 189 }
307 190
308 var removedListener = this.listeners_.splice(idx, 1)[0]; 191 this.listeners_.splice(idx, 1);
309 this.attachmentStrategy_.onRemovedListener(removedListener);
310
311 if (this.listeners_.length == 0) { 192 if (this.listeners_.length == 0) {
312 var i = allAttachedEvents.indexOf(this); 193 this.detach_(true);
313 if (i >= 0)
314 delete allAttachedEvents[i];
315 if (!this.eventName_)
316 return;
317
318 if (!attachedNamedEvents[this.eventName_]) {
319 throw new Error("chrome.Event '" + this.eventName_ +
320 "' is not attached.");
321 }
322
323 delete attachedNamedEvents[this.eventName_];
324 } 194 }
325 }; 195 };
326 196
327 // Test if the given callback is registered for this event. 197 // Test if the given callback is registered for this event.
328 chrome.Event.prototype.hasListener = function(cb) { 198 chrome.Event.prototype.hasListener = function(cb) {
329 if (!this.eventOptions_.supportsListeners) 199 if (!this.eventOptions_.supportsListeners)
330 throw new Error("This event does not support listeners."); 200 throw new Error("This event does not support listeners.");
331 return this.findListener_(cb) > -1; 201 return this.findListener_(cb) > -1;
332 }; 202 };
333 203
334 // Test if any callbacks are registered for this event. 204 // Test if any callbacks are registered for this event.
335 chrome.Event.prototype.hasListeners = function() { 205 chrome.Event.prototype.hasListeners = function() {
336 if (!this.eventOptions_.supportsListeners) 206 if (!this.eventOptions_.supportsListeners)
337 throw new Error("This event does not support listeners."); 207 throw new Error("This event does not support listeners.");
338 return this.listeners_.length > 0; 208 return this.listeners_.length > 0;
339 }; 209 };
340 210
341 // Returns the index of the given callback if registered, or -1 if not 211 // Returns the index of the given callback if registered, or -1 if not
342 // found. 212 // found.
343 chrome.Event.prototype.findListener_ = function(cb) { 213 chrome.Event.prototype.findListener_ = function(cb) {
344 for (var i = 0; i < this.listeners_.length; i++) { 214 for (var i = 0; i < this.listeners_.length; i++) {
345 if (this.listeners_[i].callback == cb) { 215 if (this.listeners_[i] == cb) {
346 return i; 216 return i;
347 } 217 }
348 } 218 }
349 219
350 return -1; 220 return -1;
351 }; 221 };
352 222
353 chrome.Event.prototype.dispatch_ = function(args, listenerIDs) { 223 // Dispatches this event object to all listeners, passing all supplied
224 // arguments to this function each listener.
225 chrome.Event.prototype.dispatch = function(varargs) {
354 if (!this.eventOptions_.supportsListeners) 226 if (!this.eventOptions_.supportsListeners)
355 throw new Error("This event does not support listeners."); 227 throw new Error("This event does not support listeners.");
228 var args = Array.prototype.slice.call(arguments);
356 var validationErrors = this.validateEventArgs_(args); 229 var validationErrors = this.validateEventArgs_(args);
357 if (validationErrors) { 230 if (validationErrors) {
358 console.error(validationErrors); 231 console.error(validationErrors);
359 return {validationErrors: validationErrors}; 232 return {validationErrors: validationErrors};
360 } 233 }
361
362 var listeners = this.attachmentStrategy_.getListenersByIDs(listenerIDs);
363
364 var results = []; 234 var results = [];
365 for (var i = 0; i < listeners.length; i++) { 235 for (var i = 0; i < this.listeners_.length; i++) {
366 try { 236 try {
367 var result = listeners[i].callback.apply(null, args); 237 var result = this.listeners_[i].apply(null, args);
368 if (result !== undefined) 238 if (result !== undefined)
369 results.push(result); 239 results.push(result);
370 } catch (e) { 240 } catch (e) {
371 console.error("Error in event handler for '" + this.eventName_ + 241 console.error("Error in event handler for '" + this.eventName_ +
372 "': " + e.message + ' ' + e.stack); 242 "': " + e.message + ' ' + e.stack);
373 } 243 }
374 } 244 }
375 if (results.length) 245 if (results.length)
376 return {results: results}; 246 return {results: results};
377 } 247 };
378 248
379 // Dispatches this event object to all listeners, passing all supplied 249 // Attaches this event object to its name. Only one object can have a given
380 // arguments to this function each listener. 250 // name.
381 chrome.Event.prototype.dispatch = function(varargs) { 251 chrome.Event.prototype.attach_ = function() {
382 return this.dispatch_(Array.prototype.slice.call(arguments), undefined); 252 AttachEvent(this.eventName_);
253 allAttachedEvents[allAttachedEvents.length] = this;
254 if (!this.eventName_)
255 return;
256
257 if (attachedNamedEvents[this.eventName_]) {
258 throw new Error("chrome.Event '" + this.eventName_ +
259 "' is already attached.");
260 }
261
262 attachedNamedEvents[this.eventName_] = this;
383 }; 263 };
384 264
385 // Detaches this event object from its name. 265 // Detaches this event object from its name.
386 chrome.Event.prototype.detach_ = function() { 266 chrome.Event.prototype.detach_ = function(manual) {
387 this.attachmentStrategy_.detach(false); 267 var i = allAttachedEvents.indexOf(this);
268 if (i >= 0)
269 delete allAttachedEvents[i];
270 DetachEvent(this.eventName_, manual);
271 if (!this.eventName_)
272 return;
273
274 if (!attachedNamedEvents[this.eventName_]) {
275 throw new Error("chrome.Event '" + this.eventName_ +
276 "' is not attached.");
277 }
278
279 delete attachedNamedEvents[this.eventName_];
388 }; 280 };
389 281
390 chrome.Event.prototype.destroy_ = function() { 282 chrome.Event.prototype.destroy_ = function() {
391 this.listeners_ = []; 283 this.listeners_ = [];
392 this.validateEventArgs_ = []; 284 this.validateEventArgs_ = [];
393 this.detach_(false); 285 this.detach_(false);
394 }; 286 };
395 287
396 chrome.Event.prototype.addRules = function(rules, opt_cb) { 288 chrome.Event.prototype.addRules = function(rules, opt_cb) {
397 if (!this.eventOptions_.supportsRules) 289 if (!this.eventOptions_.supportsRules)
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
464 chromeHidden.onUnload = new chrome.Event(); 356 chromeHidden.onUnload = new chrome.Event();
465 357
466 chromeHidden.dispatchOnLoad = 358 chromeHidden.dispatchOnLoad =
467 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); 359 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad);
468 360
469 chromeHidden.dispatchOnUnload = function() { 361 chromeHidden.dispatchOnUnload = function() {
470 chromeHidden.onUnload.dispatch(); 362 chromeHidden.onUnload.dispatch();
471 for (var i = 0; i < allAttachedEvents.length; ++i) { 363 for (var i = 0; i < allAttachedEvents.length; ++i) {
472 var event = allAttachedEvents[i]; 364 var event = allAttachedEvents[i];
473 if (event) 365 if (event)
474 event.detach_(); 366 event.detach_(false);
475 } 367 }
476 }; 368 };
477 369
478 chromeHidden.dispatchError = function(msg) { 370 chromeHidden.dispatchError = function(msg) {
479 console.error(msg); 371 console.error(msg);
480 }; 372 };
481 373
482 exports.Event = chrome.Event; 374 exports.Event = chrome.Event;
OLDNEW
« no previous file with comments | « chrome/renderer/extensions/extension_dispatcher.cc ('k') | chrome/test/data/extensions/api_test/filtered_events/manifest.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698