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 DCHECK = requireNative('logging').DCHECK; | 5 var DCHECK = requireNative('logging').DCHECK; |
| 6 // TODO(cduvall/kalman): json_schema shouldn't put things on chromeHidden. |
| 7 require('json_schema'); |
6 var eventBindingsNatives = requireNative('event_bindings'); | 8 var eventBindingsNatives = requireNative('event_bindings'); |
7 var AttachEvent = eventBindingsNatives.AttachEvent; | 9 var AttachEvent = eventBindingsNatives.AttachEvent; |
8 var DetachEvent = eventBindingsNatives.DetachEvent; | 10 var DetachEvent = eventBindingsNatives.DetachEvent; |
9 var AttachFilteredEvent = eventBindingsNatives.AttachFilteredEvent; | 11 var AttachFilteredEvent = eventBindingsNatives.AttachFilteredEvent; |
10 var DetachFilteredEvent = eventBindingsNatives.DetachFilteredEvent; | 12 var DetachFilteredEvent = eventBindingsNatives.DetachFilteredEvent; |
11 var MatchAgainstEventFilter = eventBindingsNatives.MatchAgainstEventFilter; | 13 var MatchAgainstEventFilter = eventBindingsNatives.MatchAgainstEventFilter; |
12 var sendRequest = require('sendRequest').sendRequest; | 14 var sendRequest = require('sendRequest').sendRequest; |
13 var utils = require('utils'); | 15 var utils = require('utils'); |
14 var validate = require('schemaUtils').validate; | 16 var validate = require('schemaUtils').validate; |
15 | 17 |
16 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); | 18 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); |
17 var GetExtensionAPIDefinition = | 19 var chrome = requireNative('chrome').GetChrome(); |
18 requireNative('apiDefinitions').GetExtensionAPIDefinition; | 20 var schemaRegistry = requireNative('schema_registry'); |
19 | 21 |
20 // Schemas for the rule-style functions on the events API that | 22 // Schemas for the rule-style functions on the events API that |
21 // only need to be generated occasionally, so populate them lazily. | 23 // only need to be generated occasionally, so populate them lazily. |
22 var ruleFunctionSchemas = { | 24 var ruleFunctionSchemas = { |
23 // These values are set lazily: | 25 // These values are set lazily: |
24 // addRules: {}, | 26 // addRules: {}, |
25 // getRules: {}, | 27 // getRules: {}, |
26 // removeRules: {} | 28 // removeRules: {} |
27 }; | 29 }; |
28 | 30 |
29 // This function ensures that |ruleFunctionSchemas| is populated. | 31 // This function ensures that |ruleFunctionSchemas| is populated. |
30 function ensureRuleSchemasLoaded() { | 32 function ensureRuleSchemasLoaded() { |
31 if (ruleFunctionSchemas.addRules) | 33 if (ruleFunctionSchemas.addRules) |
32 return; | 34 return; |
33 var eventsSchema = GetExtensionAPIDefinition("events")[0]; | 35 var eventsSchema = schemaRegistry.GetSchema("events"); |
34 var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event'); | 36 var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event'); |
35 | 37 |
36 ruleFunctionSchemas.addRules = | 38 ruleFunctionSchemas.addRules = |
37 utils.lookup(eventType.functions, 'name', 'addRules'); | 39 utils.lookup(eventType.functions, 'name', 'addRules'); |
38 ruleFunctionSchemas.getRules = | 40 ruleFunctionSchemas.getRules = |
39 utils.lookup(eventType.functions, 'name', 'getRules'); | 41 utils.lookup(eventType.functions, 'name', 'getRules'); |
40 ruleFunctionSchemas.removeRules = | 42 ruleFunctionSchemas.removeRules = |
41 utils.lookup(eventType.functions, 'name', 'removeRules'); | 43 utils.lookup(eventType.functions, 'name', 'removeRules'); |
42 } | 44 } |
43 | 45 |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
144 // opt_eventName is required for events that support rules. | 146 // opt_eventName is required for events that support rules. |
145 // | 147 // |
146 // Example: | 148 // Example: |
147 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); | 149 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); |
148 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); | 150 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); |
149 // chromeHidden.Event.dispatch("tab-changed", "hi"); | 151 // chromeHidden.Event.dispatch("tab-changed", "hi"); |
150 // will result in an alert dialog that says 'hi'. | 152 // will result in an alert dialog that says 'hi'. |
151 // | 153 // |
152 // If opt_eventOptions exists, it is a dictionary that contains the boolean | 154 // If opt_eventOptions exists, it is a dictionary that contains the boolean |
153 // entries "supportsListeners" and "supportsRules". | 155 // entries "supportsListeners" and "supportsRules". |
154 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { | 156 var Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { |
155 this.eventName_ = opt_eventName; | 157 this.eventName_ = opt_eventName; |
156 this.listeners_ = []; | 158 this.listeners_ = []; |
157 this.eventOptions_ = chromeHidden.parseEventOptions(opt_eventOptions); | 159 this.eventOptions_ = chromeHidden.parseEventOptions(opt_eventOptions); |
158 | 160 |
159 if (this.eventOptions_.supportsRules && !opt_eventName) | 161 if (this.eventOptions_.supportsRules && !opt_eventName) |
160 throw new Error("Events that support rules require an event name."); | 162 throw new Error("Events that support rules require an event name."); |
161 | 163 |
162 if (this.eventOptions_.supportsFilters) { | 164 if (this.eventOptions_.supportsFilters) { |
163 this.attachmentStrategy_ = new FilteredAttachmentStrategy(this); | 165 this.attachmentStrategy_ = new FilteredAttachmentStrategy(this); |
164 } else { | 166 } else { |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
219 dispatchArgs(args); | 221 dispatchArgs(args); |
220 }; | 222 }; |
221 | 223 |
222 // Test if a named event has any listeners. | 224 // Test if a named event has any listeners. |
223 chromeHidden.Event.hasListener = function(name) { | 225 chromeHidden.Event.hasListener = function(name) { |
224 return (attachedNamedEvents[name] && | 226 return (attachedNamedEvents[name] && |
225 attachedNamedEvents[name].listeners_.length > 0); | 227 attachedNamedEvents[name].listeners_.length > 0); |
226 }; | 228 }; |
227 | 229 |
228 // Registers a callback to be called when this event is dispatched. | 230 // Registers a callback to be called when this event is dispatched. |
229 chrome.Event.prototype.addListener = function(cb, filters) { | 231 Event.prototype.addListener = function(cb, filters) { |
230 if (!this.eventOptions_.supportsListeners) | 232 if (!this.eventOptions_.supportsListeners) |
231 throw new Error("This event does not support listeners."); | 233 throw new Error("This event does not support listeners."); |
232 if (this.eventOptions_.maxListeners && | 234 if (this.eventOptions_.maxListeners && |
233 this.getListenerCount() >= this.eventOptions_.maxListeners) | 235 this.getListenerCount() >= this.eventOptions_.maxListeners) |
234 throw new Error("Too many listeners for " + this.eventName_); | 236 throw new Error("Too many listeners for " + this.eventName_); |
235 if (filters) { | 237 if (filters) { |
236 if (!this.eventOptions_.supportsFilters) | 238 if (!this.eventOptions_.supportsFilters) |
237 throw new Error("This event does not support filters."); | 239 throw new Error("This event does not support filters."); |
238 if (filters.url && !(filters.url instanceof Array)) | 240 if (filters.url && !(filters.url instanceof Array)) |
239 throw new Error("filters.url should be an array"); | 241 throw new Error("filters.url should be an array"); |
240 } | 242 } |
241 var listener = {callback: cb, filters: filters}; | 243 var listener = {callback: cb, filters: filters}; |
242 this.attach_(listener); | 244 this.attach_(listener); |
243 this.listeners_.push(listener); | 245 this.listeners_.push(listener); |
244 }; | 246 }; |
245 | 247 |
246 chrome.Event.prototype.attach_ = function(listener) { | 248 Event.prototype.attach_ = function(listener) { |
247 this.attachmentStrategy_.onAddedListener(listener); | 249 this.attachmentStrategy_.onAddedListener(listener); |
248 if (this.listeners_.length == 0) { | 250 if (this.listeners_.length == 0) { |
249 allAttachedEvents[allAttachedEvents.length] = this; | 251 allAttachedEvents[allAttachedEvents.length] = this; |
250 if (!this.eventName_) | 252 if (!this.eventName_) |
251 return; | 253 return; |
252 | 254 |
253 if (attachedNamedEvents[this.eventName_]) { | 255 if (attachedNamedEvents[this.eventName_]) { |
254 throw new Error("chrome.Event '" + this.eventName_ + | 256 throw new Error("chrome.Event '" + this.eventName_ + |
255 "' is already attached."); | 257 "' is already attached."); |
256 } | 258 } |
257 | 259 |
258 attachedNamedEvents[this.eventName_] = this; | 260 attachedNamedEvents[this.eventName_] = this; |
259 } | 261 } |
260 }; | 262 }; |
261 | 263 |
262 // Unregisters a callback. | 264 // Unregisters a callback. |
263 chrome.Event.prototype.removeListener = function(cb) { | 265 Event.prototype.removeListener = function(cb) { |
264 if (!this.eventOptions_.supportsListeners) | 266 if (!this.eventOptions_.supportsListeners) |
265 throw new Error("This event does not support listeners."); | 267 throw new Error("This event does not support listeners."); |
266 var idx = this.findListener_(cb); | 268 var idx = this.findListener_(cb); |
267 if (idx == -1) { | 269 if (idx == -1) { |
268 return; | 270 return; |
269 } | 271 } |
270 | 272 |
271 var removedListener = this.listeners_.splice(idx, 1)[0]; | 273 var removedListener = this.listeners_.splice(idx, 1)[0]; |
272 this.attachmentStrategy_.onRemovedListener(removedListener); | 274 this.attachmentStrategy_.onRemovedListener(removedListener); |
273 | 275 |
274 if (this.listeners_.length == 0) { | 276 if (this.listeners_.length == 0) { |
275 var i = allAttachedEvents.indexOf(this); | 277 var i = allAttachedEvents.indexOf(this); |
276 if (i >= 0) | 278 if (i >= 0) |
277 delete allAttachedEvents[i]; | 279 delete allAttachedEvents[i]; |
278 if (!this.eventName_) | 280 if (!this.eventName_) |
279 return; | 281 return; |
280 | 282 |
281 if (!attachedNamedEvents[this.eventName_]) { | 283 if (!attachedNamedEvents[this.eventName_]) { |
282 throw new Error("chrome.Event '" + this.eventName_ + | 284 throw new Error("chrome.Event '" + this.eventName_ + |
283 "' is not attached."); | 285 "' is not attached."); |
284 } | 286 } |
285 | 287 |
286 delete attachedNamedEvents[this.eventName_]; | 288 delete attachedNamedEvents[this.eventName_]; |
287 } | 289 } |
288 }; | 290 }; |
289 | 291 |
290 // Test if the given callback is registered for this event. | 292 // Test if the given callback is registered for this event. |
291 chrome.Event.prototype.hasListener = function(cb) { | 293 Event.prototype.hasListener = function(cb) { |
292 if (!this.eventOptions_.supportsListeners) | 294 if (!this.eventOptions_.supportsListeners) |
293 throw new Error("This event does not support listeners."); | 295 throw new Error("This event does not support listeners."); |
294 return this.findListener_(cb) > -1; | 296 return this.findListener_(cb) > -1; |
295 }; | 297 }; |
296 | 298 |
297 // Test if any callbacks are registered for this event. | 299 // Test if any callbacks are registered for this event. |
298 chrome.Event.prototype.hasListeners = function() { | 300 Event.prototype.hasListeners = function() { |
299 return this.getListenerCount() > 0; | 301 return this.getListenerCount() > 0; |
300 }; | 302 }; |
301 | 303 |
302 // Return the number of listeners on this event. | 304 // Return the number of listeners on this event. |
303 chrome.Event.prototype.getListenerCount = function() { | 305 Event.prototype.getListenerCount = function() { |
304 if (!this.eventOptions_.supportsListeners) | 306 if (!this.eventOptions_.supportsListeners) |
305 throw new Error("This event does not support listeners."); | 307 throw new Error("This event does not support listeners."); |
306 return this.listeners_.length; | 308 return this.listeners_.length; |
307 }; | 309 }; |
308 | 310 |
309 // Returns the index of the given callback if registered, or -1 if not | 311 // Returns the index of the given callback if registered, or -1 if not |
310 // found. | 312 // found. |
311 chrome.Event.prototype.findListener_ = function(cb) { | 313 Event.prototype.findListener_ = function(cb) { |
312 for (var i = 0; i < this.listeners_.length; i++) { | 314 for (var i = 0; i < this.listeners_.length; i++) { |
313 if (this.listeners_[i].callback == cb) { | 315 if (this.listeners_[i].callback == cb) { |
314 return i; | 316 return i; |
315 } | 317 } |
316 } | 318 } |
317 | 319 |
318 return -1; | 320 return -1; |
319 }; | 321 }; |
320 | 322 |
321 chrome.Event.prototype.dispatch_ = function(args, listenerIDs) { | 323 Event.prototype.dispatch_ = function(args, listenerIDs) { |
322 if (!this.eventOptions_.supportsListeners) | 324 if (!this.eventOptions_.supportsListeners) |
323 throw new Error("This event does not support listeners."); | 325 throw new Error("This event does not support listeners."); |
324 var validationErrors = this.validateEventArgs_(args); | 326 var validationErrors = this.validateEventArgs_(args); |
325 if (validationErrors) { | 327 if (validationErrors) { |
326 console.error(validationErrors); | 328 console.error(validationErrors); |
327 return {validationErrors: validationErrors}; | 329 return {validationErrors: validationErrors}; |
328 } | 330 } |
329 | 331 |
330 // Make a copy of the listeners in case the listener list is modified | 332 // Make a copy of the listeners in case the listener list is modified |
331 // while dispatching the event. | 333 // while dispatching the event. |
332 var listeners = | 334 var listeners = |
333 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice(); | 335 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice(); |
334 | 336 |
335 var results = []; | 337 var results = []; |
336 for (var i = 0; i < listeners.length; i++) { | 338 for (var i = 0; i < listeners.length; i++) { |
337 try { | 339 try { |
338 var result = this.dispatchToListener(listeners[i].callback, args); | 340 var result = this.dispatchToListener(listeners[i].callback, args); |
339 if (result !== undefined) | 341 if (result !== undefined) |
340 results.push(result); | 342 results.push(result); |
341 } catch (e) { | 343 } catch (e) { |
342 console.error("Error in event handler for '" + this.eventName_ + | 344 console.error("Error in event handler for '" + this.eventName_ + |
343 "': " + e.message + ' ' + e.stack); | 345 "': " + e.message + ' ' + e.stack); |
344 } | 346 } |
345 } | 347 } |
346 if (results.length) | 348 if (results.length) |
347 return {results: results}; | 349 return {results: results}; |
348 } | 350 } |
349 | 351 |
350 // Can be overridden to support custom dispatching. | 352 // Can be overridden to support custom dispatching. |
351 chrome.Event.prototype.dispatchToListener = function(callback, args) { | 353 Event.prototype.dispatchToListener = function(callback, args) { |
352 return callback.apply(null, args); | 354 return callback.apply(null, args); |
353 } | 355 } |
354 | 356 |
355 // Dispatches this event object to all listeners, passing all supplied | 357 // Dispatches this event object to all listeners, passing all supplied |
356 // arguments to this function each listener. | 358 // arguments to this function each listener. |
357 chrome.Event.prototype.dispatch = function(varargs) { | 359 Event.prototype.dispatch = function(varargs) { |
358 return this.dispatch_(Array.prototype.slice.call(arguments), undefined); | 360 return this.dispatch_(Array.prototype.slice.call(arguments), undefined); |
359 }; | 361 }; |
360 | 362 |
361 // Detaches this event object from its name. | 363 // Detaches this event object from its name. |
362 chrome.Event.prototype.detach_ = function() { | 364 Event.prototype.detach_ = function() { |
363 this.attachmentStrategy_.detach(false); | 365 this.attachmentStrategy_.detach(false); |
364 }; | 366 }; |
365 | 367 |
366 chrome.Event.prototype.destroy_ = function() { | 368 Event.prototype.destroy_ = function() { |
367 this.listeners_ = []; | 369 this.listeners_ = []; |
368 this.validateEventArgs_ = []; | 370 this.validateEventArgs_ = []; |
369 this.detach_(false); | 371 this.detach_(false); |
370 }; | 372 }; |
371 | 373 |
372 chrome.Event.prototype.addRules = function(rules, opt_cb) { | 374 Event.prototype.addRules = function(rules, opt_cb) { |
373 if (!this.eventOptions_.supportsRules) | 375 if (!this.eventOptions_.supportsRules) |
374 throw new Error("This event does not support rules."); | 376 throw new Error("This event does not support rules."); |
375 | 377 |
376 // Takes a list of JSON datatype identifiers and returns a schema fragment | 378 // Takes a list of JSON datatype identifiers and returns a schema fragment |
377 // that verifies that a JSON object corresponds to an array of only these | 379 // that verifies that a JSON object corresponds to an array of only these |
378 // data types. | 380 // data types. |
379 function buildArrayOfChoicesSchema(typesList) { | 381 function buildArrayOfChoicesSchema(typesList) { |
380 return { | 382 return { |
381 'type': 'array', | 383 'type': 'array', |
382 'items': { | 384 'items': { |
(...skipping 29 matching lines...) Expand all Loading... |
412 | 414 |
413 ensureRuleSchemasLoaded(); | 415 ensureRuleSchemasLoaded(); |
414 // We remove the first parameter from the validation to give the user more | 416 // We remove the first parameter from the validation to give the user more |
415 // meaningful error messages. | 417 // meaningful error messages. |
416 validate([rules, opt_cb], | 418 validate([rules, opt_cb], |
417 ruleFunctionSchemas.addRules.parameters.slice().splice(1)); | 419 ruleFunctionSchemas.addRules.parameters.slice().splice(1)); |
418 sendRequest("events.addRules", [this.eventName_, rules, opt_cb], | 420 sendRequest("events.addRules", [this.eventName_, rules, opt_cb], |
419 ruleFunctionSchemas.addRules.parameters); | 421 ruleFunctionSchemas.addRules.parameters); |
420 } | 422 } |
421 | 423 |
422 chrome.Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { | 424 Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { |
423 if (!this.eventOptions_.supportsRules) | 425 if (!this.eventOptions_.supportsRules) |
424 throw new Error("This event does not support rules."); | 426 throw new Error("This event does not support rules."); |
425 ensureRuleSchemasLoaded(); | 427 ensureRuleSchemasLoaded(); |
426 // We remove the first parameter from the validation to give the user more | 428 // We remove the first parameter from the validation to give the user more |
427 // meaningful error messages. | 429 // meaningful error messages. |
428 validate([ruleIdentifiers, opt_cb], | 430 validate([ruleIdentifiers, opt_cb], |
429 ruleFunctionSchemas.removeRules.parameters.slice().splice(1)); | 431 ruleFunctionSchemas.removeRules.parameters.slice().splice(1)); |
430 sendRequest("events.removeRules", | 432 sendRequest("events.removeRules", |
431 [this.eventName_, ruleIdentifiers, opt_cb], | 433 [this.eventName_, ruleIdentifiers, opt_cb], |
432 ruleFunctionSchemas.removeRules.parameters); | 434 ruleFunctionSchemas.removeRules.parameters); |
433 } | 435 } |
434 | 436 |
435 chrome.Event.prototype.getRules = function(ruleIdentifiers, cb) { | 437 Event.prototype.getRules = function(ruleIdentifiers, cb) { |
436 if (!this.eventOptions_.supportsRules) | 438 if (!this.eventOptions_.supportsRules) |
437 throw new Error("This event does not support rules."); | 439 throw new Error("This event does not support rules."); |
438 ensureRuleSchemasLoaded(); | 440 ensureRuleSchemasLoaded(); |
439 // We remove the first parameter from the validation to give the user more | 441 // We remove the first parameter from the validation to give the user more |
440 // meaningful error messages. | 442 // meaningful error messages. |
441 validate([ruleIdentifiers, cb], | 443 validate([ruleIdentifiers, cb], |
442 ruleFunctionSchemas.getRules.parameters.slice().splice(1)); | 444 ruleFunctionSchemas.getRules.parameters.slice().splice(1)); |
443 | 445 |
444 sendRequest("events.getRules", | 446 sendRequest("events.getRules", |
445 [this.eventName_, ruleIdentifiers, cb], | 447 [this.eventName_, ruleIdentifiers, cb], |
446 ruleFunctionSchemas.getRules.parameters); | 448 ruleFunctionSchemas.getRules.parameters); |
447 } | 449 } |
448 | 450 |
449 // Special load events: we don't use the DOM unload because that slows | 451 // Special load events: we don't use the DOM unload because that slows |
450 // down tab shutdown. On the other hand, onUnload might not always fire, | 452 // down tab shutdown. On the other hand, onUnload might not always fire, |
451 // since Chrome will terminate renderers on shutdown (SuddenTermination). | 453 // since Chrome will terminate renderers on shutdown (SuddenTermination). |
452 chromeHidden.onLoad = new chrome.Event(); | 454 chromeHidden.onLoad = new Event(); |
453 chromeHidden.onUnload = new chrome.Event(); | 455 chromeHidden.onUnload = new Event(); |
454 | 456 |
455 chromeHidden.dispatchOnLoad = | 457 chromeHidden.dispatchOnLoad = |
456 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); | 458 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); |
457 | 459 |
458 chromeHidden.dispatchOnUnload = function() { | 460 chromeHidden.dispatchOnUnload = function() { |
459 chromeHidden.onUnload.dispatch(); | 461 chromeHidden.onUnload.dispatch(); |
460 chromeHidden.wasUnloaded = true; | 462 chromeHidden.wasUnloaded = true; |
461 | 463 |
462 for (var i = 0; i < allAttachedEvents.length; ++i) { | 464 for (var i = 0; i < allAttachedEvents.length; ++i) { |
463 var event = allAttachedEvents[i]; | 465 var event = allAttachedEvents[i]; |
464 if (event) | 466 if (event) |
465 event.detach_(); | 467 event.detach_(); |
466 } | 468 } |
467 }; | 469 }; |
468 | 470 |
469 chromeHidden.dispatchError = function(msg) { | 471 chromeHidden.dispatchError = function(msg) { |
470 console.error(msg); | 472 console.error(msg); |
471 }; | 473 }; |
472 | 474 |
473 exports.Event = chrome.Event; | 475 chrome.Event = Event; |
OLD | NEW |