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 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
182 // opt_eventName is required for events that support rules. | 184 // opt_eventName is required for events that support rules. |
183 // | 185 // |
184 // Example: | 186 // Example: |
185 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); | 187 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); |
186 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); | 188 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); |
187 // chromeHidden.Event.dispatch("tab-changed", "hi"); | 189 // chromeHidden.Event.dispatch("tab-changed", "hi"); |
188 // will result in an alert dialog that says 'hi'. | 190 // will result in an alert dialog that says 'hi'. |
189 // | 191 // |
190 // If opt_eventOptions exists, it is a dictionary that contains the boolean | 192 // If opt_eventOptions exists, it is a dictionary that contains the boolean |
191 // entries "supportsListeners" and "supportsRules". | 193 // entries "supportsListeners" and "supportsRules". |
192 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { | 194 var Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { |
193 this.eventName_ = opt_eventName; | 195 this.eventName_ = opt_eventName; |
194 this.listeners_ = []; | 196 this.listeners_ = []; |
195 this.eventOptions_ = chromeHidden.parseEventOptions(opt_eventOptions); | 197 this.eventOptions_ = chromeHidden.parseEventOptions(opt_eventOptions); |
196 | 198 |
197 if (this.eventOptions_.supportsRules && !opt_eventName) | 199 if (this.eventOptions_.supportsRules && !opt_eventName) |
198 throw new Error("Events that support rules require an event name."); | 200 throw new Error("Events that support rules require an event name."); |
199 | 201 |
200 if (this.eventOptions_.supportsFilters) { | 202 if (this.eventOptions_.supportsFilters) { |
201 this.attachmentStrategy_ = new FilteredAttachmentStrategy(this); | 203 this.attachmentStrategy_ = new FilteredAttachmentStrategy(this); |
202 } else { | 204 } else { |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
257 dispatchArgs(args); | 259 dispatchArgs(args); |
258 }; | 260 }; |
259 | 261 |
260 // Test if a named event has any listeners. | 262 // Test if a named event has any listeners. |
261 chromeHidden.Event.hasListener = function(name) { | 263 chromeHidden.Event.hasListener = function(name) { |
262 return (attachedNamedEvents[name] && | 264 return (attachedNamedEvents[name] && |
263 attachedNamedEvents[name].listeners_.length > 0); | 265 attachedNamedEvents[name].listeners_.length > 0); |
264 }; | 266 }; |
265 | 267 |
266 // Registers a callback to be called when this event is dispatched. | 268 // Registers a callback to be called when this event is dispatched. |
267 chrome.Event.prototype.addListener = function(cb, filters) { | 269 Event.prototype.addListener = function(cb, filters) { |
268 if (!this.eventOptions_.supportsListeners) | 270 if (!this.eventOptions_.supportsListeners) |
269 throw new Error("This event does not support listeners."); | 271 throw new Error("This event does not support listeners."); |
270 if (this.eventOptions_.maxListeners && | 272 if (this.eventOptions_.maxListeners && |
271 this.getListenerCount() >= this.eventOptions_.maxListeners) | 273 this.getListenerCount() >= this.eventOptions_.maxListeners) |
272 throw new Error("Too many listeners for " + this.eventName_); | 274 throw new Error("Too many listeners for " + this.eventName_); |
273 if (filters) { | 275 if (filters) { |
274 if (!this.eventOptions_.supportsFilters) | 276 if (!this.eventOptions_.supportsFilters) |
275 throw new Error("This event does not support filters."); | 277 throw new Error("This event does not support filters."); |
276 if (filters.url && !(filters.url instanceof Array)) | 278 if (filters.url && !(filters.url instanceof Array)) |
277 throw new Error("filters.url should be an array"); | 279 throw new Error("filters.url should be an array"); |
278 } | 280 } |
279 var listener = {callback: cb, filters: filters}; | 281 var listener = {callback: cb, filters: filters}; |
280 this.attach_(listener); | 282 this.attach_(listener); |
281 this.listeners_.push(listener); | 283 this.listeners_.push(listener); |
282 }; | 284 }; |
283 | 285 |
284 chrome.Event.prototype.attach_ = function(listener) { | 286 Event.prototype.attach_ = function(listener) { |
285 this.attachmentStrategy_.onAddedListener(listener); | 287 this.attachmentStrategy_.onAddedListener(listener); |
286 if (this.listeners_.length == 0) { | 288 if (this.listeners_.length == 0) { |
287 allAttachedEvents[allAttachedEvents.length] = this; | 289 allAttachedEvents[allAttachedEvents.length] = this; |
288 if (!this.eventName_) | 290 if (!this.eventName_) |
289 return; | 291 return; |
290 | 292 |
291 if (attachedNamedEvents[this.eventName_]) { | 293 if (attachedNamedEvents[this.eventName_]) { |
292 throw new Error("chrome.Event '" + this.eventName_ + | 294 throw new Error("chrome.Event '" + this.eventName_ + |
293 "' is already attached."); | 295 "' is already attached."); |
294 } | 296 } |
295 | 297 |
296 attachedNamedEvents[this.eventName_] = this; | 298 attachedNamedEvents[this.eventName_] = this; |
297 } | 299 } |
298 }; | 300 }; |
299 | 301 |
300 // Unregisters a callback. | 302 // Unregisters a callback. |
301 chrome.Event.prototype.removeListener = function(cb) { | 303 Event.prototype.removeListener = function(cb) { |
302 if (!this.eventOptions_.supportsListeners) | 304 if (!this.eventOptions_.supportsListeners) |
303 throw new Error("This event does not support listeners."); | 305 throw new Error("This event does not support listeners."); |
304 var idx = this.findListener_(cb); | 306 var idx = this.findListener_(cb); |
305 if (idx == -1) { | 307 if (idx == -1) { |
306 return; | 308 return; |
307 } | 309 } |
308 | 310 |
309 var removedListener = this.listeners_.splice(idx, 1)[0]; | 311 var removedListener = this.listeners_.splice(idx, 1)[0]; |
310 this.attachmentStrategy_.onRemovedListener(removedListener); | 312 this.attachmentStrategy_.onRemovedListener(removedListener); |
311 | 313 |
312 if (this.listeners_.length == 0) { | 314 if (this.listeners_.length == 0) { |
313 var i = allAttachedEvents.indexOf(this); | 315 var i = allAttachedEvents.indexOf(this); |
314 if (i >= 0) | 316 if (i >= 0) |
315 delete allAttachedEvents[i]; | 317 delete allAttachedEvents[i]; |
316 if (!this.eventName_) | 318 if (!this.eventName_) |
317 return; | 319 return; |
318 | 320 |
319 if (!attachedNamedEvents[this.eventName_]) { | 321 if (!attachedNamedEvents[this.eventName_]) { |
320 throw new Error("chrome.Event '" + this.eventName_ + | 322 throw new Error("chrome.Event '" + this.eventName_ + |
321 "' is not attached."); | 323 "' is not attached."); |
322 } | 324 } |
323 | 325 |
324 delete attachedNamedEvents[this.eventName_]; | 326 delete attachedNamedEvents[this.eventName_]; |
325 } | 327 } |
326 }; | 328 }; |
327 | 329 |
328 // Test if the given callback is registered for this event. | 330 // Test if the given callback is registered for this event. |
329 chrome.Event.prototype.hasListener = function(cb) { | 331 Event.prototype.hasListener = function(cb) { |
330 if (!this.eventOptions_.supportsListeners) | 332 if (!this.eventOptions_.supportsListeners) |
331 throw new Error("This event does not support listeners."); | 333 throw new Error("This event does not support listeners."); |
332 return this.findListener_(cb) > -1; | 334 return this.findListener_(cb) > -1; |
333 }; | 335 }; |
334 | 336 |
335 // Test if any callbacks are registered for this event. | 337 // Test if any callbacks are registered for this event. |
336 chrome.Event.prototype.hasListeners = function() { | 338 Event.prototype.hasListeners = function() { |
337 return this.getListenerCount() > 0; | 339 return this.getListenerCount() > 0; |
338 }; | 340 }; |
339 | 341 |
340 // Return the number of listeners on this event. | 342 // Return the number of listeners on this event. |
341 chrome.Event.prototype.getListenerCount = function() { | 343 Event.prototype.getListenerCount = function() { |
342 if (!this.eventOptions_.supportsListeners) | 344 if (!this.eventOptions_.supportsListeners) |
343 throw new Error("This event does not support listeners."); | 345 throw new Error("This event does not support listeners."); |
344 return this.listeners_.length; | 346 return this.listeners_.length; |
345 }; | 347 }; |
346 | 348 |
347 // Returns the index of the given callback if registered, or -1 if not | 349 // Returns the index of the given callback if registered, or -1 if not |
348 // found. | 350 // found. |
349 chrome.Event.prototype.findListener_ = function(cb) { | 351 Event.prototype.findListener_ = function(cb) { |
350 for (var i = 0; i < this.listeners_.length; i++) { | 352 for (var i = 0; i < this.listeners_.length; i++) { |
351 if (this.listeners_[i].callback == cb) { | 353 if (this.listeners_[i].callback == cb) { |
352 return i; | 354 return i; |
353 } | 355 } |
354 } | 356 } |
355 | 357 |
356 return -1; | 358 return -1; |
357 }; | 359 }; |
358 | 360 |
359 chrome.Event.prototype.dispatch_ = function(args, listenerIDs) { | 361 Event.prototype.dispatch_ = function(args, listenerIDs) { |
360 if (!this.eventOptions_.supportsListeners) | 362 if (!this.eventOptions_.supportsListeners) |
361 throw new Error("This event does not support listeners."); | 363 throw new Error("This event does not support listeners."); |
362 var validationErrors = this.validateEventArgs_(args); | 364 var validationErrors = this.validateEventArgs_(args); |
363 if (validationErrors) { | 365 if (validationErrors) { |
364 console.error(validationErrors); | 366 console.error(validationErrors); |
365 return {validationErrors: validationErrors}; | 367 return {validationErrors: validationErrors}; |
366 } | 368 } |
367 | 369 |
368 // Make a copy of the listeners in case the listener list is modified | 370 // Make a copy of the listeners in case the listener list is modified |
369 // while dispatching the event. | 371 // while dispatching the event. |
370 var listeners = | 372 var listeners = |
371 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice(); | 373 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice(); |
372 | 374 |
373 var results = []; | 375 var results = []; |
374 for (var i = 0; i < listeners.length; i++) { | 376 for (var i = 0; i < listeners.length; i++) { |
375 try { | 377 try { |
376 var result = this.dispatchToListener(listeners[i].callback, args); | 378 var result = this.dispatchToListener(listeners[i].callback, args); |
377 if (result !== undefined) | 379 if (result !== undefined) |
378 results.push(result); | 380 results.push(result); |
379 } catch (e) { | 381 } catch (e) { |
380 console.error("Error in event handler for '" + this.eventName_ + | 382 console.error("Error in event handler for '" + this.eventName_ + |
381 "': " + e.message + ' ' + e.stack); | 383 "': " + e.message + ' ' + e.stack); |
382 } | 384 } |
383 } | 385 } |
384 if (results.length) | 386 if (results.length) |
385 return {results: results}; | 387 return {results: results}; |
386 } | 388 } |
387 | 389 |
388 // Can be overridden to support custom dispatching. | 390 // Can be overridden to support custom dispatching. |
389 chrome.Event.prototype.dispatchToListener = function(callback, args) { | 391 Event.prototype.dispatchToListener = function(callback, args) { |
390 return callback.apply(null, args); | 392 return callback.apply(null, args); |
391 } | 393 } |
392 | 394 |
393 // Dispatches this event object to all listeners, passing all supplied | 395 // Dispatches this event object to all listeners, passing all supplied |
394 // arguments to this function each listener. | 396 // arguments to this function each listener. |
395 chrome.Event.prototype.dispatch = function(varargs) { | 397 Event.prototype.dispatch = function(varargs) { |
396 return this.dispatch_(Array.prototype.slice.call(arguments), undefined); | 398 return this.dispatch_(Array.prototype.slice.call(arguments), undefined); |
397 }; | 399 }; |
398 | 400 |
399 // Detaches this event object from its name. | 401 // Detaches this event object from its name. |
400 chrome.Event.prototype.detach_ = function() { | 402 Event.prototype.detach_ = function() { |
401 this.attachmentStrategy_.detach(false); | 403 this.attachmentStrategy_.detach(false); |
402 }; | 404 }; |
403 | 405 |
404 chrome.Event.prototype.destroy_ = function() { | 406 Event.prototype.destroy_ = function() { |
405 this.listeners_ = []; | 407 this.listeners_ = []; |
406 this.validateEventArgs_ = []; | 408 this.validateEventArgs_ = []; |
407 this.detach_(false); | 409 this.detach_(false); |
408 }; | 410 }; |
409 | 411 |
410 chrome.Event.prototype.addRules = function(rules, opt_cb) { | 412 Event.prototype.addRules = function(rules, opt_cb) { |
411 if (!this.eventOptions_.supportsRules) | 413 if (!this.eventOptions_.supportsRules) |
412 throw new Error("This event does not support rules."); | 414 throw new Error("This event does not support rules."); |
413 | 415 |
414 // Takes a list of JSON datatype identifiers and returns a schema fragment | 416 // Takes a list of JSON datatype identifiers and returns a schema fragment |
415 // that verifies that a JSON object corresponds to an array of only these | 417 // that verifies that a JSON object corresponds to an array of only these |
416 // data types. | 418 // data types. |
417 function buildArrayOfChoicesSchema(typesList) { | 419 function buildArrayOfChoicesSchema(typesList) { |
418 return { | 420 return { |
419 'type': 'array', | 421 'type': 'array', |
420 'items': { | 422 'items': { |
(...skipping 29 matching lines...) Expand all Loading... |
450 | 452 |
451 ensureRuleSchemasLoaded(); | 453 ensureRuleSchemasLoaded(); |
452 // We remove the first parameter from the validation to give the user more | 454 // We remove the first parameter from the validation to give the user more |
453 // meaningful error messages. | 455 // meaningful error messages. |
454 validate([rules, opt_cb], | 456 validate([rules, opt_cb], |
455 ruleFunctionSchemas.addRules.parameters.slice().splice(1)); | 457 ruleFunctionSchemas.addRules.parameters.slice().splice(1)); |
456 sendRequest("events.addRules", [this.eventName_, rules, opt_cb], | 458 sendRequest("events.addRules", [this.eventName_, rules, opt_cb], |
457 ruleFunctionSchemas.addRules.parameters); | 459 ruleFunctionSchemas.addRules.parameters); |
458 } | 460 } |
459 | 461 |
460 chrome.Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { | 462 Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { |
461 if (!this.eventOptions_.supportsRules) | 463 if (!this.eventOptions_.supportsRules) |
462 throw new Error("This event does not support rules."); | 464 throw new Error("This event does not support rules."); |
463 ensureRuleSchemasLoaded(); | 465 ensureRuleSchemasLoaded(); |
464 // We remove the first parameter from the validation to give the user more | 466 // We remove the first parameter from the validation to give the user more |
465 // meaningful error messages. | 467 // meaningful error messages. |
466 validate([ruleIdentifiers, opt_cb], | 468 validate([ruleIdentifiers, opt_cb], |
467 ruleFunctionSchemas.removeRules.parameters.slice().splice(1)); | 469 ruleFunctionSchemas.removeRules.parameters.slice().splice(1)); |
468 sendRequest("events.removeRules", | 470 sendRequest("events.removeRules", |
469 [this.eventName_, ruleIdentifiers, opt_cb], | 471 [this.eventName_, ruleIdentifiers, opt_cb], |
470 ruleFunctionSchemas.removeRules.parameters); | 472 ruleFunctionSchemas.removeRules.parameters); |
471 } | 473 } |
472 | 474 |
473 chrome.Event.prototype.getRules = function(ruleIdentifiers, cb) { | 475 Event.prototype.getRules = function(ruleIdentifiers, cb) { |
474 if (!this.eventOptions_.supportsRules) | 476 if (!this.eventOptions_.supportsRules) |
475 throw new Error("This event does not support rules."); | 477 throw new Error("This event does not support rules."); |
476 ensureRuleSchemasLoaded(); | 478 ensureRuleSchemasLoaded(); |
477 // We remove the first parameter from the validation to give the user more | 479 // We remove the first parameter from the validation to give the user more |
478 // meaningful error messages. | 480 // meaningful error messages. |
479 validate([ruleIdentifiers, cb], | 481 validate([ruleIdentifiers, cb], |
480 ruleFunctionSchemas.getRules.parameters.slice().splice(1)); | 482 ruleFunctionSchemas.getRules.parameters.slice().splice(1)); |
481 | 483 |
482 sendRequest("events.getRules", | 484 sendRequest("events.getRules", |
483 [this.eventName_, ruleIdentifiers, cb], | 485 [this.eventName_, ruleIdentifiers, cb], |
484 ruleFunctionSchemas.getRules.parameters); | 486 ruleFunctionSchemas.getRules.parameters); |
485 } | 487 } |
486 | 488 |
487 // Special load events: we don't use the DOM unload because that slows | 489 // Special load events: we don't use the DOM unload because that slows |
488 // down tab shutdown. On the other hand, onUnload might not always fire, | 490 // down tab shutdown. On the other hand, onUnload might not always fire, |
489 // since Chrome will terminate renderers on shutdown (SuddenTermination). | 491 // since Chrome will terminate renderers on shutdown (SuddenTermination). |
490 chromeHidden.onLoad = new chrome.Event(); | 492 chromeHidden.onLoad = new Event(); |
491 chromeHidden.onUnload = new chrome.Event(); | 493 chromeHidden.onUnload = new Event(); |
492 | 494 |
493 chromeHidden.dispatchOnLoad = | 495 chromeHidden.dispatchOnLoad = |
494 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); | 496 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); |
495 | 497 |
496 chromeHidden.dispatchOnUnload = function() { | 498 chromeHidden.dispatchOnUnload = function() { |
497 chromeHidden.onUnload.dispatch(); | 499 chromeHidden.onUnload.dispatch(); |
498 for (var i = 0; i < allAttachedEvents.length; ++i) { | 500 for (var i = 0; i < allAttachedEvents.length; ++i) { |
499 var event = allAttachedEvents[i]; | 501 var event = allAttachedEvents[i]; |
500 if (event) | 502 if (event) |
501 event.detach_(); | 503 event.detach_(); |
502 } | 504 } |
503 }; | 505 }; |
504 | 506 |
505 chromeHidden.dispatchError = function(msg) { | 507 chromeHidden.dispatchError = function(msg) { |
506 console.error(msg); | 508 console.error(msg); |
507 }; | 509 }; |
508 | 510 |
509 exports.Event = chrome.Event; | 511 chrome.Event = Event; |
OLD | NEW |