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

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

Issue 10381143: Add filtered event support to event.js. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: respond to comments Created 8 years, 7 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
« no previous file with comments | « chrome/renderer/extensions/event_unittest.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
8 var sendRequest = require('sendRequest').sendRequest; 10 var sendRequest = require('sendRequest').sendRequest;
9 var utils = require('utils'); 11 var utils = require('utils');
10 12
11 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); 13 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden();
12 var GetExtensionAPIDefinition = 14 var GetExtensionAPIDefinition =
13 requireNative('apiDefinitions').GetExtensionAPIDefinition; 15 requireNative('apiDefinitions').GetExtensionAPIDefinition;
14 16
15 // Schemas for the rule-style functions on the events API that 17 // Schemas for the rule-style functions on the events API that
16 // only need to be generated occasionally, so populate them lazily. 18 // only need to be generated occasionally, so populate them lazily.
17 var ruleFunctionSchemas = { 19 var ruleFunctionSchemas = {
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
67 $Array.prototype.toJSON = customizedArrayToJSON; 69 $Array.prototype.toJSON = customizedArrayToJSON;
68 } 70 }
69 } 71 }
70 }; 72 };
71 73
72 this.parse = function(thing) { 74 this.parse = function(thing) {
73 return $jsonParse(thing); 75 return $jsonParse(thing);
74 }; 76 };
75 })(); 77 })();
76 78
79 // Handles adding/removing/dispatching listeners for unfiltered events.
80 var UnfilteredAttachmentStrategy = function(event) {
81 this.event_ = event;
82 };
83
84 UnfilteredAttachmentStrategy.prototype.onAddedListener =
85 function(listener) {
86 // Only attach / detach on the first / last listener removed.
87 if (this.event_.listeners_.length > 1)
88 return;
89 AttachEvent(this.event_.eventName_);
90 allAttachedEvents[allAttachedEvents.length] = this.event_;
91 if (!this.event_.eventName_)
92 return;
93
94 if (attachedNamedEvents[this.event_.eventName_]) {
95 throw new Error("chrome.Event '" + this.event_.eventName_ +
96 "' is already attached.");
97 }
98
99 attachedNamedEvents[this.event_.eventName_] = this.event_;
100 };
101
102 UnfilteredAttachmentStrategy.prototype.onRemovedListener =
103 function(listener) {
104 if (this.event_.listeners_.length == 0)
105 this.detach(true);
106 };
107
108 UnfilteredAttachmentStrategy.prototype.detach =
109 function(manual) {
110 var i = allAttachedEvents.indexOf(this.event_);
111 if (i >= 0)
112 delete allAttachedEvents[i];
113 DetachEvent(this.event_.eventName_, manual);
114 if (!this.event_.eventName_)
115 return;
116
117 if (!attachedNamedEvents[this.event_.eventName_]) {
118 throw new Error("chrome.Event '" + this.event_.eventName_ +
119 "' is not attached.");
120 }
121
122 delete attachedNamedEvents[this.event_.eventName_];
123 };
124
125 var FilteredAttachmentStrategy = function(event) {
126 this.event_ = event;
127 this.listenerMap_ = {};
128 };
129
130 FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) {
131 var id = AttachFilteredEvent(this.event_.eventName_,
132 listener.filters || []);
133 listener.id = id;
134 this.listenerMap_[id] = listener;
135 };
136
137 FilteredAttachmentStrategy.prototype.onRemovedListener = function(listener) {
138 this.detachListener(listener, true);
139 };
140
141 FilteredAttachmentStrategy.prototype.detachListener =
142 function(listener, manual) {
143 if (listener.id == undefined)
144 throw new Error("listener.id undefined - '" + listener + "'");
145 var id = listener.id;
146 delete this.listenerMap_[id];
147 DetachFilteredEvent(id, manual);
148 };
149
150 FilteredAttachmentStrategy.prototype.detach = function(manual) {
151 for (var i in this.listenerMap_)
152 this.detachListener(this.listenerMap_[i], manual);
153 };
154
77 // Event object. If opt_eventName is provided, this object represents 155 // Event object. If opt_eventName is provided, this object represents
78 // the unique instance of that named event, and dispatching an event 156 // the unique instance of that named event, and dispatching an event
79 // with that name will route through this object's listeners. Note that 157 // with that name will route through this object's listeners. Note that
80 // opt_eventName is required for events that support rules. 158 // opt_eventName is required for events that support rules.
81 // 159 //
82 // Example: 160 // Example:
83 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); 161 // chrome.tabs.onChanged = new chrome.Event("tab-changed");
84 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); 162 // chrome.tabs.onChanged.addListener(function(data) { alert(data); });
85 // chromeHidden.Event.dispatch("tab-changed", "hi"); 163 // chromeHidden.Event.dispatch("tab-changed", "hi");
86 // will result in an alert dialog that says 'hi'. 164 // will result in an alert dialog that says 'hi'.
87 // 165 //
88 // If opt_eventOptions exists, it is a dictionary that contains the boolean 166 // If opt_eventOptions exists, it is a dictionary that contains the boolean
89 // entries "supportsListeners" and "supportsRules". 167 // entries "supportsListeners" and "supportsRules".
90 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { 168 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) {
91 this.eventName_ = opt_eventName; 169 this.eventName_ = opt_eventName;
92 this.listeners_ = []; 170 this.listeners_ = [];
93 this.eventOptions_ = opt_eventOptions || 171 this.eventOptions_ = opt_eventOptions ||
94 {"supportsListeners": true, "supportsRules": false}; 172 {supportsListeners: true,
173 supportsRules: false,
174 supportsFilters: false,
175 };
95 176
96 if (this.eventOptions_.supportsRules && !opt_eventName) 177 if (this.eventOptions_.supportsRules && !opt_eventName)
97 throw new Error("Events that support rules require an event name."); 178 throw new Error("Events that support rules require an event name.");
98 179
180 if (this.eventOptions_.supportsFilters) {
181 this.attachmentStrategy_ = new FilteredAttachmentStrategy(this);
182 } else {
183 this.attachmentStrategy_ = new UnfilteredAttachmentStrategy(this);
184 }
185
99 // Validate event arguments (the data that is passed to the callbacks) 186 // Validate event arguments (the data that is passed to the callbacks)
100 // if we are in debug. 187 // if we are in debug.
101 if (opt_argSchemas && 188 if (opt_argSchemas &&
102 chromeHidden.validateCallbacks && 189 chromeHidden.validateCallbacks &&
103 chromeHidden.validate) { 190 chromeHidden.validate) {
104 191
105 this.validateEventArgs_ = function(args) { 192 this.validateEventArgs_ = function(args) {
106 try { 193 try {
107 chromeHidden.validate(args, opt_argSchemas); 194 chromeHidden.validate(args, opt_argSchemas);
108 } catch (exception) { 195 } catch (exception) {
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
158 } 245 }
159 }; 246 };
160 247
161 // Test if a named event has any listeners. 248 // Test if a named event has any listeners.
162 chromeHidden.Event.hasListener = function(name) { 249 chromeHidden.Event.hasListener = function(name) {
163 return (attachedNamedEvents[name] && 250 return (attachedNamedEvents[name] &&
164 attachedNamedEvents[name].listeners_.length > 0); 251 attachedNamedEvents[name].listeners_.length > 0);
165 }; 252 };
166 253
167 // Registers a callback to be called when this event is dispatched. 254 // Registers a callback to be called when this event is dispatched.
168 chrome.Event.prototype.addListener = function(cb) { 255 chrome.Event.prototype.addListener = function(cb, filters) {
169 if (!this.eventOptions_.supportsListeners) 256 if (!this.eventOptions_.supportsListeners)
170 throw new Error("This event does not support listeners."); 257 throw new Error("This event does not support listeners.");
171 if (this.listeners_.length == 0) { 258 if (filters) {
172 this.attach_(); 259 if (!this.eventOptions_.supportsFilters)
260 throw new Error("This event does not support filters.");
261 if (!(filters instanceof Array))
262 throw new Error("filters should be an array");
173 } 263 }
174 this.listeners_.push(cb); 264 var listener = {callback: cb, filters: filters};
265 this.listeners_.push(listener);
266 this.attachmentStrategy_.onAddedListener(listener);
175 }; 267 };
176 268
177 // Unregisters a callback. 269 // Unregisters a callback.
178 chrome.Event.prototype.removeListener = function(cb) { 270 chrome.Event.prototype.removeListener = function(cb) {
179 if (!this.eventOptions_.supportsListeners) 271 if (!this.eventOptions_.supportsListeners)
180 throw new Error("This event does not support listeners."); 272 throw new Error("This event does not support listeners.");
181 var idx = this.findListener_(cb); 273 var idx = this.findListener_(cb);
182 if (idx == -1) { 274 if (idx == -1) {
183 return; 275 return;
184 } 276 }
185 277
186 this.listeners_.splice(idx, 1); 278 var removedListener = this.listeners_.splice(idx, 1)[0];
187 if (this.listeners_.length == 0) { 279 this.attachmentStrategy_.onRemovedListener(removedListener);
188 this.detach_(true);
189 }
190 }; 280 };
191 281
192 // Test if the given callback is registered for this event. 282 // Test if the given callback is registered for this event.
193 chrome.Event.prototype.hasListener = function(cb) { 283 chrome.Event.prototype.hasListener = function(cb) {
194 if (!this.eventOptions_.supportsListeners) 284 if (!this.eventOptions_.supportsListeners)
195 throw new Error("This event does not support listeners."); 285 throw new Error("This event does not support listeners.");
196 return this.findListener_(cb) > -1; 286 return this.findListener_(cb) > -1;
197 }; 287 };
198 288
199 // Test if any callbacks are registered for this event. 289 // Test if any callbacks are registered for this event.
200 chrome.Event.prototype.hasListeners = function() { 290 chrome.Event.prototype.hasListeners = function() {
201 if (!this.eventOptions_.supportsListeners) 291 if (!this.eventOptions_.supportsListeners)
202 throw new Error("This event does not support listeners."); 292 throw new Error("This event does not support listeners.");
203 return this.listeners_.length > 0; 293 return this.listeners_.length > 0;
204 }; 294 };
205 295
206 // Returns the index of the given callback if registered, or -1 if not 296 // Returns the index of the given callback if registered, or -1 if not
207 // found. 297 // found.
208 chrome.Event.prototype.findListener_ = function(cb) { 298 chrome.Event.prototype.findListener_ = function(cb) {
209 for (var i = 0; i < this.listeners_.length; i++) { 299 for (var i = 0; i < this.listeners_.length; i++) {
210 if (this.listeners_[i] == cb) { 300 if (this.listeners_[i].callback == cb) {
211 return i; 301 return i;
212 } 302 }
213 } 303 }
214 304
215 return -1; 305 return -1;
216 }; 306 };
217 307
218 // Dispatches this event object to all listeners, passing all supplied 308 // Dispatches this event object to all listeners, passing all supplied
219 // arguments to this function each listener. 309 // arguments to this function each listener.
220 chrome.Event.prototype.dispatch = function(varargs) { 310 chrome.Event.prototype.dispatch = function(varargs) {
221 if (!this.eventOptions_.supportsListeners) 311 if (!this.eventOptions_.supportsListeners)
222 throw new Error("This event does not support listeners."); 312 throw new Error("This event does not support listeners.");
223 var args = Array.prototype.slice.call(arguments); 313 var args = Array.prototype.slice.call(arguments);
224 var validationErrors = this.validateEventArgs_(args); 314 var validationErrors = this.validateEventArgs_(args);
225 if (validationErrors) { 315 if (validationErrors) {
226 return {validationErrors: validationErrors}; 316 return {validationErrors: validationErrors};
227 } 317 }
228 var results = []; 318 var results = [];
229 for (var i = 0; i < this.listeners_.length; i++) { 319 for (var i = 0; i < this.listeners_.length; i++) {
230 try { 320 try {
231 var result = this.listeners_[i].apply(null, args); 321 var result = this.listeners_[i].callback.apply(null, args);
232 if (result !== undefined) 322 if (result !== undefined)
233 results.push(result); 323 results.push(result);
234 } catch (e) { 324 } catch (e) {
235 console.error("Error in event handler for '" + this.eventName_ + 325 console.error("Error in event handler for '" + this.eventName_ +
236 "': " + e.message + ' ' + e.stack); 326 "': " + e.message + ' ' + e.stack);
237 } 327 }
238 } 328 }
239 if (results.length) 329 if (results.length)
240 return {results: results}; 330 return {results: results};
241 }; 331 };
242 332
243 // Attaches this event object to its name. Only one object can have a given
244 // name.
245 chrome.Event.prototype.attach_ = function() {
246 AttachEvent(this.eventName_);
247 allAttachedEvents[allAttachedEvents.length] = this;
248 if (!this.eventName_)
249 return;
250
251 if (attachedNamedEvents[this.eventName_]) {
252 throw new Error("chrome.Event '" + this.eventName_ +
253 "' is already attached.");
254 }
255
256 attachedNamedEvents[this.eventName_] = this;
257 };
258
259 // Detaches this event object from its name. 333 // Detaches this event object from its name.
260 chrome.Event.prototype.detach_ = function(manual) { 334 chrome.Event.prototype.detach_ = function() {
261 var i = allAttachedEvents.indexOf(this); 335 this.attachmentStrategy_.detach(false);
262 if (i >= 0)
263 delete allAttachedEvents[i];
264 DetachEvent(this.eventName_, manual);
265 if (!this.eventName_)
266 return;
267
268 if (!attachedNamedEvents[this.eventName_]) {
269 throw new Error("chrome.Event '" + this.eventName_ +
270 "' is not attached.");
271 }
272
273 delete attachedNamedEvents[this.eventName_];
274 }; 336 };
275 337
276 chrome.Event.prototype.destroy_ = function() { 338 chrome.Event.prototype.destroy_ = function() {
277 this.listeners_ = []; 339 this.listeners_ = [];
278 this.validateEventArgs_ = []; 340 this.validateEventArgs_ = [];
279 this.detach_(false); 341 this.detach_(false);
280 }; 342 };
281 343
344 // Gets the declarative API object, or undefined if this extension doesn't
345 // have access to it.
346 //
347 // This is defined as a function (rather than a variable) because it isn't
348 // accessible until the schema bindings have been generated.
349 function getDeclarativeAPI() {
350 return chromeHidden.internalAPIs.declarative;
351 }
352
282 chrome.Event.prototype.addRules = function(rules, opt_cb) { 353 chrome.Event.prototype.addRules = function(rules, opt_cb) {
283 if (!this.eventOptions_.supportsRules) 354 if (!this.eventOptions_.supportsRules)
284 throw new Error("This event does not support rules."); 355 throw new Error("This event does not support rules.");
285 356
286 // Takes a list of JSON datatype identifiers and returns a schema fragment 357 // Takes a list of JSON datatype identifiers and returns a schema fragment
287 // that verifies that a JSON object corresponds to an array of only these 358 // that verifies that a JSON object corresponds to an array of only these
288 // data types. 359 // data types.
289 function buildArrayOfChoicesSchema(typesList) { 360 function buildArrayOfChoicesSchema(typesList) {
290 return { 361 return {
291 'type': 'array', 362 'type': 'array',
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
350 chromeHidden.onUnload = new chrome.Event(); 421 chromeHidden.onUnload = new chrome.Event();
351 422
352 chromeHidden.dispatchOnLoad = 423 chromeHidden.dispatchOnLoad =
353 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); 424 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad);
354 425
355 chromeHidden.dispatchOnUnload = function() { 426 chromeHidden.dispatchOnUnload = function() {
356 chromeHidden.onUnload.dispatch(); 427 chromeHidden.onUnload.dispatch();
357 for (var i = 0; i < allAttachedEvents.length; ++i) { 428 for (var i = 0; i < allAttachedEvents.length; ++i) {
358 var event = allAttachedEvents[i]; 429 var event = allAttachedEvents[i];
359 if (event) 430 if (event)
360 event.detach_(false); 431 event.detach_();
361 } 432 }
362 }; 433 };
363 434
364 chromeHidden.dispatchError = function(msg) { 435 chromeHidden.dispatchError = function(msg) {
365 console.error(msg); 436 console.error(msg);
366 }; 437 };
367 438
368 exports.Event = chrome.Event; 439 exports.Event = chrome.Event;
OLDNEW
« no previous file with comments | « chrome/renderer/extensions/event_unittest.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698