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 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
220 dispatchArgs(args); | 222 dispatchArgs(args); |
221 }; | 223 }; |
222 | 224 |
223 // Test if a named event has any listeners. | 225 // Test if a named event has any listeners. |
224 chromeHidden.Event.hasListener = function(name) { | 226 chromeHidden.Event.hasListener = function(name) { |
225 return (attachedNamedEvents[name] && | 227 return (attachedNamedEvents[name] && |
226 attachedNamedEvents[name].listeners_.length > 0); | 228 attachedNamedEvents[name].listeners_.length > 0); |
227 }; | 229 }; |
228 | 230 |
229 // Registers a callback to be called when this event is dispatched. | 231 // Registers a callback to be called when this event is dispatched. |
230 chrome.Event.prototype.addListener = function(cb, filters) { | 232 Event.prototype.addListener = function(cb, filters) { |
231 if (!this.eventOptions_.supportsListeners) | 233 if (!this.eventOptions_.supportsListeners) |
232 throw new Error("This event does not support listeners."); | 234 throw new Error("This event does not support listeners."); |
233 if (this.eventOptions_.maxListeners && | 235 if (this.eventOptions_.maxListeners && |
234 this.getListenerCount() >= this.eventOptions_.maxListeners) | 236 this.getListenerCount() >= this.eventOptions_.maxListeners) |
235 throw new Error("Too many listeners for " + this.eventName_); | 237 throw new Error("Too many listeners for " + this.eventName_); |
236 if (filters) { | 238 if (filters) { |
237 if (!this.eventOptions_.supportsFilters) | 239 if (!this.eventOptions_.supportsFilters) |
238 throw new Error("This event does not support filters."); | 240 throw new Error("This event does not support filters."); |
239 if (filters.url && !(filters.url instanceof Array)) | 241 if (filters.url && !(filters.url instanceof Array)) |
240 throw new Error("filters.url should be an array"); | 242 throw new Error("filters.url should be an array"); |
241 } | 243 } |
242 var listener = {callback: cb, filters: filters}; | 244 var listener = {callback: cb, filters: filters}; |
243 this.attach_(listener); | 245 this.attach_(listener); |
244 this.listeners_.push(listener); | 246 this.listeners_.push(listener); |
245 }; | 247 }; |
246 | 248 |
247 chrome.Event.prototype.attach_ = function(listener) { | 249 Event.prototype.attach_ = function(listener) { |
248 this.attachmentStrategy_.onAddedListener(listener); | 250 this.attachmentStrategy_.onAddedListener(listener); |
249 if (this.listeners_.length == 0) { | 251 if (this.listeners_.length == 0) { |
250 allAttachedEvents[allAttachedEvents.length] = this; | 252 allAttachedEvents[allAttachedEvents.length] = this; |
251 if (!this.eventName_) | 253 if (!this.eventName_) |
252 return; | 254 return; |
253 | 255 |
254 if (attachedNamedEvents[this.eventName_]) { | 256 if (attachedNamedEvents[this.eventName_]) { |
255 throw new Error("chrome.Event '" + this.eventName_ + | 257 throw new Error("chrome.Event '" + this.eventName_ + |
256 "' is already attached."); | 258 "' is already attached."); |
257 } | 259 } |
258 | 260 |
259 attachedNamedEvents[this.eventName_] = this; | 261 attachedNamedEvents[this.eventName_] = this; |
260 } | 262 } |
261 }; | 263 }; |
262 | 264 |
263 // Unregisters a callback. | 265 // Unregisters a callback. |
264 chrome.Event.prototype.removeListener = function(cb) { | 266 Event.prototype.removeListener = function(cb) { |
265 if (!this.eventOptions_.supportsListeners) | 267 if (!this.eventOptions_.supportsListeners) |
266 throw new Error("This event does not support listeners."); | 268 throw new Error("This event does not support listeners."); |
267 var idx = this.findListener_(cb); | 269 var idx = this.findListener_(cb); |
268 if (idx == -1) { | 270 if (idx == -1) { |
269 return; | 271 return; |
270 } | 272 } |
271 | 273 |
272 var removedListener = this.listeners_.splice(idx, 1)[0]; | 274 var removedListener = this.listeners_.splice(idx, 1)[0]; |
273 this.attachmentStrategy_.onRemovedListener(removedListener); | 275 this.attachmentStrategy_.onRemovedListener(removedListener); |
274 | 276 |
275 if (this.listeners_.length == 0) { | 277 if (this.listeners_.length == 0) { |
276 var i = allAttachedEvents.indexOf(this); | 278 var i = allAttachedEvents.indexOf(this); |
277 if (i >= 0) | 279 if (i >= 0) |
278 delete allAttachedEvents[i]; | 280 delete allAttachedEvents[i]; |
279 if (!this.eventName_) | 281 if (!this.eventName_) |
280 return; | 282 return; |
281 | 283 |
282 if (!attachedNamedEvents[this.eventName_]) { | 284 if (!attachedNamedEvents[this.eventName_]) { |
283 throw new Error("chrome.Event '" + this.eventName_ + | 285 throw new Error("chrome.Event '" + this.eventName_ + |
284 "' is not attached."); | 286 "' is not attached."); |
285 } | 287 } |
286 | 288 |
287 delete attachedNamedEvents[this.eventName_]; | 289 delete attachedNamedEvents[this.eventName_]; |
288 } | 290 } |
289 }; | 291 }; |
290 | 292 |
291 // Test if the given callback is registered for this event. | 293 // Test if the given callback is registered for this event. |
292 chrome.Event.prototype.hasListener = function(cb) { | 294 Event.prototype.hasListener = function(cb) { |
293 if (!this.eventOptions_.supportsListeners) | 295 if (!this.eventOptions_.supportsListeners) |
294 throw new Error("This event does not support listeners."); | 296 throw new Error("This event does not support listeners."); |
295 return this.findListener_(cb) > -1; | 297 return this.findListener_(cb) > -1; |
296 }; | 298 }; |
297 | 299 |
298 // Test if any callbacks are registered for this event. | 300 // Test if any callbacks are registered for this event. |
299 chrome.Event.prototype.hasListeners = function() { | 301 Event.prototype.hasListeners = function() { |
300 return this.getListenerCount() > 0; | 302 return this.getListenerCount() > 0; |
301 }; | 303 }; |
302 | 304 |
303 // Return the number of listeners on this event. | 305 // Return the number of listeners on this event. |
304 chrome.Event.prototype.getListenerCount = function() { | 306 Event.prototype.getListenerCount = function() { |
305 if (!this.eventOptions_.supportsListeners) | 307 if (!this.eventOptions_.supportsListeners) |
306 throw new Error("This event does not support listeners."); | 308 throw new Error("This event does not support listeners."); |
307 return this.listeners_.length; | 309 return this.listeners_.length; |
308 }; | 310 }; |
309 | 311 |
310 // Returns the index of the given callback if registered, or -1 if not | 312 // Returns the index of the given callback if registered, or -1 if not |
311 // found. | 313 // found. |
312 chrome.Event.prototype.findListener_ = function(cb) { | 314 Event.prototype.findListener_ = function(cb) { |
313 for (var i = 0; i < this.listeners_.length; i++) { | 315 for (var i = 0; i < this.listeners_.length; i++) { |
314 if (this.listeners_[i].callback == cb) { | 316 if (this.listeners_[i].callback == cb) { |
315 return i; | 317 return i; |
316 } | 318 } |
317 } | 319 } |
318 | 320 |
319 return -1; | 321 return -1; |
320 }; | 322 }; |
321 | 323 |
322 chrome.Event.prototype.dispatch_ = function(args, listenerIDs) { | 324 Event.prototype.dispatch_ = function(args, listenerIDs) { |
323 if (!this.eventOptions_.supportsListeners) | 325 if (!this.eventOptions_.supportsListeners) |
324 throw new Error("This event does not support listeners."); | 326 throw new Error("This event does not support listeners."); |
325 var validationErrors = this.validateEventArgs_(args); | 327 var validationErrors = this.validateEventArgs_(args); |
326 if (validationErrors) { | 328 if (validationErrors) { |
327 console.error(validationErrors); | 329 console.error(validationErrors); |
328 return {validationErrors: validationErrors}; | 330 return {validationErrors: validationErrors}; |
329 } | 331 } |
330 | 332 |
331 // Make a copy of the listeners in case the listener list is modified | 333 // Make a copy of the listeners in case the listener list is modified |
332 // while dispatching the event. | 334 // while dispatching the event. |
333 var listeners = | 335 var listeners = |
334 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice(); | 336 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice(); |
335 | 337 |
336 var results = []; | 338 var results = []; |
337 for (var i = 0; i < listeners.length; i++) { | 339 for (var i = 0; i < listeners.length; i++) { |
338 try { | 340 try { |
339 var result = this.dispatchToListener(listeners[i].callback, args); | 341 var result = this.dispatchToListener(listeners[i].callback, args); |
340 if (result !== undefined) | 342 if (result !== undefined) |
341 results.push(result); | 343 results.push(result); |
342 } catch (e) { | 344 } catch (e) { |
343 console.error("Error in event handler for '" + this.eventName_ + | 345 console.error("Error in event handler for '" + this.eventName_ + |
344 "': " + e.message + ' ' + e.stack); | 346 "': " + e.message + ' ' + e.stack); |
345 } | 347 } |
346 } | 348 } |
347 if (results.length) | 349 if (results.length) |
348 return {results: results}; | 350 return {results: results}; |
349 } | 351 } |
350 | 352 |
351 // Can be overridden to support custom dispatching. | 353 // Can be overridden to support custom dispatching. |
352 chrome.Event.prototype.dispatchToListener = function(callback, args) { | 354 Event.prototype.dispatchToListener = function(callback, args) { |
353 return callback.apply(null, args); | 355 return callback.apply(null, args); |
354 } | 356 } |
355 | 357 |
356 // Dispatches this event object to all listeners, passing all supplied | 358 // Dispatches this event object to all listeners, passing all supplied |
357 // arguments to this function each listener. | 359 // arguments to this function each listener. |
358 chrome.Event.prototype.dispatch = function(varargs) { | 360 Event.prototype.dispatch = function(varargs) { |
359 return this.dispatch_(Array.prototype.slice.call(arguments), undefined); | 361 return this.dispatch_(Array.prototype.slice.call(arguments), undefined); |
360 }; | 362 }; |
361 | 363 |
362 // Detaches this event object from its name. | 364 // Detaches this event object from its name. |
363 chrome.Event.prototype.detach_ = function() { | 365 Event.prototype.detach_ = function() { |
364 this.attachmentStrategy_.detach(false); | 366 this.attachmentStrategy_.detach(false); |
365 }; | 367 }; |
366 | 368 |
367 chrome.Event.prototype.destroy_ = function() { | 369 Event.prototype.destroy_ = function() { |
368 this.listeners_ = []; | 370 this.listeners_ = []; |
369 this.validateEventArgs_ = []; | 371 this.validateEventArgs_ = []; |
370 this.detach_(false); | 372 this.detach_(false); |
371 }; | 373 }; |
372 | 374 |
373 chrome.Event.prototype.addRules = function(rules, opt_cb) { | 375 Event.prototype.addRules = function(rules, opt_cb) { |
374 if (!this.eventOptions_.supportsRules) | 376 if (!this.eventOptions_.supportsRules) |
375 throw new Error("This event does not support rules."); | 377 throw new Error("This event does not support rules."); |
376 | 378 |
377 // Takes a list of JSON datatype identifiers and returns a schema fragment | 379 // Takes a list of JSON datatype identifiers and returns a schema fragment |
378 // that verifies that a JSON object corresponds to an array of only these | 380 // that verifies that a JSON object corresponds to an array of only these |
379 // data types. | 381 // data types. |
380 function buildArrayOfChoicesSchema(typesList) { | 382 function buildArrayOfChoicesSchema(typesList) { |
381 return { | 383 return { |
382 'type': 'array', | 384 'type': 'array', |
383 'items': { | 385 'items': { |
(...skipping 29 matching lines...) Expand all Loading... |
413 | 415 |
414 ensureRuleSchemasLoaded(); | 416 ensureRuleSchemasLoaded(); |
415 // We remove the first parameter from the validation to give the user more | 417 // We remove the first parameter from the validation to give the user more |
416 // meaningful error messages. | 418 // meaningful error messages. |
417 validate([rules, opt_cb], | 419 validate([rules, opt_cb], |
418 ruleFunctionSchemas.addRules.parameters.slice().splice(1)); | 420 ruleFunctionSchemas.addRules.parameters.slice().splice(1)); |
419 sendRequest("events.addRules", [this.eventName_, rules, opt_cb], | 421 sendRequest("events.addRules", [this.eventName_, rules, opt_cb], |
420 ruleFunctionSchemas.addRules.parameters); | 422 ruleFunctionSchemas.addRules.parameters); |
421 } | 423 } |
422 | 424 |
423 chrome.Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { | 425 Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { |
424 if (!this.eventOptions_.supportsRules) | 426 if (!this.eventOptions_.supportsRules) |
425 throw new Error("This event does not support rules."); | 427 throw new Error("This event does not support rules."); |
426 ensureRuleSchemasLoaded(); | 428 ensureRuleSchemasLoaded(); |
427 // We remove the first parameter from the validation to give the user more | 429 // We remove the first parameter from the validation to give the user more |
428 // meaningful error messages. | 430 // meaningful error messages. |
429 validate([ruleIdentifiers, opt_cb], | 431 validate([ruleIdentifiers, opt_cb], |
430 ruleFunctionSchemas.removeRules.parameters.slice().splice(1)); | 432 ruleFunctionSchemas.removeRules.parameters.slice().splice(1)); |
431 sendRequest("events.removeRules", | 433 sendRequest("events.removeRules", |
432 [this.eventName_, ruleIdentifiers, opt_cb], | 434 [this.eventName_, ruleIdentifiers, opt_cb], |
433 ruleFunctionSchemas.removeRules.parameters); | 435 ruleFunctionSchemas.removeRules.parameters); |
434 } | 436 } |
435 | 437 |
436 chrome.Event.prototype.getRules = function(ruleIdentifiers, cb) { | 438 Event.prototype.getRules = function(ruleIdentifiers, cb) { |
437 if (!this.eventOptions_.supportsRules) | 439 if (!this.eventOptions_.supportsRules) |
438 throw new Error("This event does not support rules."); | 440 throw new Error("This event does not support rules."); |
439 ensureRuleSchemasLoaded(); | 441 ensureRuleSchemasLoaded(); |
440 // We remove the first parameter from the validation to give the user more | 442 // We remove the first parameter from the validation to give the user more |
441 // meaningful error messages. | 443 // meaningful error messages. |
442 validate([ruleIdentifiers, cb], | 444 validate([ruleIdentifiers, cb], |
443 ruleFunctionSchemas.getRules.parameters.slice().splice(1)); | 445 ruleFunctionSchemas.getRules.parameters.slice().splice(1)); |
444 | 446 |
445 sendRequest("events.getRules", | 447 sendRequest("events.getRules", |
446 [this.eventName_, ruleIdentifiers, cb], | 448 [this.eventName_, ruleIdentifiers, cb], |
447 ruleFunctionSchemas.getRules.parameters); | 449 ruleFunctionSchemas.getRules.parameters); |
448 } | 450 } |
449 | 451 |
450 // Special load events: we don't use the DOM unload because that slows | 452 // Special load events: we don't use the DOM unload because that slows |
451 // down tab shutdown. On the other hand, onUnload might not always fire, | 453 // down tab shutdown. On the other hand, onUnload might not always fire, |
452 // since Chrome will terminate renderers on shutdown (SuddenTermination). | 454 // since Chrome will terminate renderers on shutdown (SuddenTermination). |
453 chromeHidden.onLoad = new chrome.Event(); | 455 chromeHidden.onLoad = new Event(); |
454 chromeHidden.onUnload = new chrome.Event(); | 456 chromeHidden.onUnload = new Event(); |
455 | 457 |
456 chromeHidden.dispatchOnLoad = | 458 chromeHidden.dispatchOnLoad = |
457 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); | 459 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); |
458 | 460 |
459 chromeHidden.dispatchOnUnload = function() { | 461 chromeHidden.dispatchOnUnload = function() { |
460 chromeHidden.onUnload.dispatch(); | 462 chromeHidden.onUnload.dispatch(); |
461 chromeHidden.wasUnloaded = true; | 463 chromeHidden.wasUnloaded = true; |
462 | 464 |
463 for (var i = 0; i < allAttachedEvents.length; ++i) { | 465 for (var i = 0; i < allAttachedEvents.length; ++i) { |
464 var event = allAttachedEvents[i]; | 466 var event = allAttachedEvents[i]; |
465 if (event) | 467 if (event) |
466 event.detach_(); | 468 event.detach_(); |
467 } | 469 } |
468 }; | 470 }; |
469 | 471 |
470 chromeHidden.dispatchError = function(msg) { | 472 chromeHidden.dispatchError = function(msg) { |
471 console.error(msg); | 473 console.error(msg); |
472 }; | 474 }; |
473 | 475 |
474 exports.Event = chrome.Event; | 476 chrome.Event = Event; |
OLD | NEW |