| 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 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 sendRequest = require('sendRequest').sendRequest; | |
| 9 var utils = require('utils'); | |
| 10 | 8 |
| 11 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); | 9 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); |
| 12 var GetExtensionAPIDefinition = | |
| 13 requireNative('apiDefinitions').GetExtensionAPIDefinition; | |
| 14 | |
| 15 // Schemas for the rule-style functions on the events API that | |
| 16 // only need to be generated occasionally, so populate them lazily. | |
| 17 var ruleFunctionSchemas = { | |
| 18 // These values are set lazily: | |
| 19 // addRules: {}, | |
| 20 // getRules: {}, | |
| 21 // removeRules: {} | |
| 22 }; | |
| 23 | |
| 24 // This function ensures that |ruleFunctionSchemas| is populated. | |
| 25 function ensureRuleSchemasLoaded() { | |
| 26 if (ruleFunctionSchemas.addRules) | |
| 27 return; | |
| 28 var eventsSchema = GetExtensionAPIDefinition("events")[0]; | |
| 29 var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event'); | |
| 30 | |
| 31 ruleFunctionSchemas.addRules = | |
| 32 utils.lookup(eventType.functions, 'name', 'addRules'); | |
| 33 ruleFunctionSchemas.getRules = | |
| 34 utils.lookup(eventType.functions, 'name', 'getRules'); | |
| 35 ruleFunctionSchemas.removeRules = | |
| 36 utils.lookup(eventType.functions, 'name', 'removeRules'); | |
| 37 } | |
| 38 | 10 |
| 39 // Local implementation of JSON.parse & JSON.stringify that protect us | 11 // Local implementation of JSON.parse & JSON.stringify that protect us |
| 40 // from being clobbered by an extension. | 12 // from being clobbered by an extension. |
| 41 // | 13 // |
| 42 // TODO(aa): This makes me so sad. We shouldn't need it, as we can just pass | 14 // TODO(aa): This makes me so sad. We shouldn't need it, as we can just pass |
| 43 // Values directly over IPC without serializing to strings and use | 15 // Values directly over IPC without serializing to strings and use |
| 44 // JSONValueConverter. | 16 // JSONValueConverter. |
| 45 chromeHidden.JSON = new (function() { | 17 chromeHidden.JSON = new (function() { |
| 46 var $Object = Object; | 18 var $Object = Object; |
| 47 var $Array = Array; | 19 var $Array = Array; |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 89 // entries "supportsListeners" and "supportsRules". | 61 // entries "supportsListeners" and "supportsRules". |
| 90 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { | 62 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { |
| 91 this.eventName_ = opt_eventName; | 63 this.eventName_ = opt_eventName; |
| 92 this.listeners_ = []; | 64 this.listeners_ = []; |
| 93 this.eventOptions_ = opt_eventOptions || | 65 this.eventOptions_ = opt_eventOptions || |
| 94 {"supportsListeners": true, "supportsRules": false}; | 66 {"supportsListeners": true, "supportsRules": false}; |
| 95 | 67 |
| 96 if (this.eventOptions_.supportsRules && !opt_eventName) | 68 if (this.eventOptions_.supportsRules && !opt_eventName) |
| 97 throw new Error("Events that support rules require an event name."); | 69 throw new Error("Events that support rules require an event name."); |
| 98 | 70 |
| 99 // Validate event arguments (the data that is passed to the callbacks) | 71 // Validate event parameters if we are in debug. |
| 100 // if we are in debug. | |
| 101 if (opt_argSchemas && | 72 if (opt_argSchemas && |
| 102 chromeHidden.validateCallbacks && | 73 chromeHidden.validateCallbacks && |
| 103 chromeHidden.validate) { | 74 chromeHidden.validate) { |
| 104 | 75 |
| 105 this.validateEventArgs_ = function(args) { | 76 this.validate_ = function(args) { |
| 106 try { | 77 try { |
| 107 chromeHidden.validate(args, opt_argSchemas); | 78 chromeHidden.validate(args, opt_argSchemas); |
| 108 } catch (exception) { | 79 } catch (exception) { |
| 109 return "Event validation error during " + opt_eventName + " -- " + | 80 return "Event validation error during " + opt_eventName + " -- " + |
| 110 exception; | 81 exception; |
| 111 } | 82 } |
| 112 }; | 83 }; |
| 113 } else { | 84 } else { |
| 114 this.validateEventArgs_ = function() {} | 85 this.validate_ = function() {} |
| 115 } | 86 } |
| 116 }; | 87 }; |
| 117 | 88 |
| 118 // A map of event names to the event object that is registered to that name. | 89 // A map of event names to the event object that is registered to that name. |
| 119 var attachedNamedEvents = {}; | 90 var attachedNamedEvents = {}; |
| 120 | 91 |
| 121 // An array of all attached event objects, used for detaching on unload. | 92 // An array of all attached event objects, used for detaching on unload. |
| 122 var allAttachedEvents = []; | 93 var allAttachedEvents = []; |
| 123 | 94 |
| 124 // A map of functions that massage event arguments before they are dispatched. | 95 // A map of functions that massage event arguments before they are dispatched. |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 214 | 185 |
| 215 return -1; | 186 return -1; |
| 216 }; | 187 }; |
| 217 | 188 |
| 218 // Dispatches this event object to all listeners, passing all supplied | 189 // Dispatches this event object to all listeners, passing all supplied |
| 219 // arguments to this function each listener. | 190 // arguments to this function each listener. |
| 220 chrome.Event.prototype.dispatch = function(varargs) { | 191 chrome.Event.prototype.dispatch = function(varargs) { |
| 221 if (!this.eventOptions_.supportsListeners) | 192 if (!this.eventOptions_.supportsListeners) |
| 222 throw new Error("This event does not support listeners."); | 193 throw new Error("This event does not support listeners."); |
| 223 var args = Array.prototype.slice.call(arguments); | 194 var args = Array.prototype.slice.call(arguments); |
| 224 var validationErrors = this.validateEventArgs_(args); | 195 var validationErrors = this.validate_(args); |
| 225 if (validationErrors) { | 196 if (validationErrors) { |
| 226 return {validationErrors: validationErrors}; | 197 return {validationErrors: validationErrors}; |
| 227 } | 198 } |
| 228 var results = []; | 199 var results = []; |
| 229 for (var i = 0; i < this.listeners_.length; i++) { | 200 for (var i = 0; i < this.listeners_.length; i++) { |
| 230 try { | 201 try { |
| 231 var result = this.listeners_[i].apply(null, args); | 202 var result = this.listeners_[i].apply(null, args); |
| 232 if (result !== undefined) | 203 if (result !== undefined) |
| 233 results.push(result); | 204 results.push(result); |
| 234 } catch (e) { | 205 } catch (e) { |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 268 if (!attachedNamedEvents[this.eventName_]) { | 239 if (!attachedNamedEvents[this.eventName_]) { |
| 269 throw new Error("chrome.Event '" + this.eventName_ + | 240 throw new Error("chrome.Event '" + this.eventName_ + |
| 270 "' is not attached."); | 241 "' is not attached."); |
| 271 } | 242 } |
| 272 | 243 |
| 273 delete attachedNamedEvents[this.eventName_]; | 244 delete attachedNamedEvents[this.eventName_]; |
| 274 }; | 245 }; |
| 275 | 246 |
| 276 chrome.Event.prototype.destroy_ = function() { | 247 chrome.Event.prototype.destroy_ = function() { |
| 277 this.listeners_ = []; | 248 this.listeners_ = []; |
| 278 this.validateEventArgs_ = []; | 249 this.validate_ = []; |
| 279 this.detach_(false); | 250 this.detach_(false); |
| 280 }; | 251 }; |
| 281 | 252 |
| 253 // Gets the declarative API object, or undefined if this extension doesn't |
| 254 // have access to it. |
| 255 // |
| 256 // This is defined as a function (rather than a variable) because it isn't |
| 257 // accessible until the schema bindings have been generated. |
| 258 function getDeclarativeAPI() { |
| 259 return chromeHidden.internalAPIs.declarative; |
| 260 } |
| 261 |
| 282 chrome.Event.prototype.addRules = function(rules, opt_cb) { | 262 chrome.Event.prototype.addRules = function(rules, opt_cb) { |
| 283 if (!this.eventOptions_.supportsRules) | 263 if (!this.eventOptions_.supportsRules) |
| 284 throw new Error("This event does not support rules."); | 264 throw new Error("This event does not support rules."); |
| 285 | 265 if (!getDeclarativeAPI()) { |
| 286 // Takes a list of JSON datatype identifiers and returns a schema fragment | 266 throw new Error("You must have permission to use the declarative " + |
| 287 // that verifies that a JSON object corresponds to an array of only these | 267 "API to support rules in events"); |
| 288 // data types. | |
| 289 function buildArrayOfChoicesSchema(typesList) { | |
| 290 return { | |
| 291 'type': 'array', | |
| 292 'items': { | |
| 293 'choices': typesList.map(function(el) {return {'$ref': el};}) | |
| 294 } | |
| 295 }; | |
| 296 }; | |
| 297 | |
| 298 // Validate conditions and actions against specific schemas of this | |
| 299 // event object type. | |
| 300 // |rules| is an array of JSON objects that follow the Rule type of the | |
| 301 // declarative extension APIs. |conditions| is an array of JSON type | |
| 302 // identifiers that are allowed to occur in the conditions attribute of each | |
| 303 // rule. Likewise, |actions| is an array of JSON type identifiers that are | |
| 304 // allowed to occur in the actions attribute of each rule. | |
| 305 function validateRules(rules, conditions, actions) { | |
| 306 var conditionsSchema = buildArrayOfChoicesSchema(conditions); | |
| 307 var actionsSchema = buildArrayOfChoicesSchema(actions); | |
| 308 rules.forEach(function(rule) { | |
| 309 chromeHidden.validate([rule.conditions], [conditionsSchema]); | |
| 310 chromeHidden.validate([rule.actions], [actionsSchema]); | |
| 311 }) | |
| 312 }; | |
| 313 | |
| 314 if (!this.eventOptions_.conditions || !this.eventOptions_.actions) { | |
| 315 throw new Error('Event ' + this.eventName_ + ' misses conditions or ' + | |
| 316 'actions in the API specification.'); | |
| 317 } | 268 } |
| 318 | 269 getDeclarativeAPI().addRules(this.eventName_, rules, opt_cb); |
| 319 validateRules(rules, | |
| 320 this.eventOptions_.conditions, | |
| 321 this.eventOptions_.actions); | |
| 322 | |
| 323 ensureRuleSchemasLoaded(); | |
| 324 sendRequest("events.addRules", [this.eventName_, rules, opt_cb], | |
| 325 ruleFunctionSchemas.addRules.parameters); | |
| 326 } | 270 } |
| 327 | 271 |
| 328 chrome.Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { | 272 chrome.Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { |
| 329 if (!this.eventOptions_.supportsRules) | 273 if (!this.eventOptions_.supportsRules) |
| 330 throw new Error("This event does not support rules."); | 274 throw new Error("This event does not support rules."); |
| 331 ensureRuleSchemasLoaded(); | 275 if (!getDeclarativeAPI()) { |
| 332 sendRequest("events.removeRules", | 276 throw new Error("You must have permission to use the declarative " + |
| 333 [this.eventName_, ruleIdentifiers, opt_cb], | 277 "API to support rules in events"); |
| 334 ruleFunctionSchemas.removeRules.parameters); | 278 } |
| 279 getDeclarativeAPI().removeRules( |
| 280 this.eventName_, ruleIdentifiers, opt_cb); |
| 335 } | 281 } |
| 336 | 282 |
| 337 chrome.Event.prototype.getRules = function(ruleIdentifiers, cb) { | 283 chrome.Event.prototype.getRules = function(ruleIdentifiers, cb) { |
| 338 if (!this.eventOptions_.supportsRules) | 284 if (!this.eventOptions_.supportsRules) |
| 339 throw new Error("This event does not support rules."); | 285 throw new Error("This event does not support rules."); |
| 340 ensureRuleSchemasLoaded(); | 286 if (!getDeclarativeAPI()) { |
| 341 sendRequest("events.getRules", | 287 throw new Error("You must have permission to use the declarative " + |
| 342 [this.eventName_, ruleIdentifiers, cb], | 288 "API to support rules in events"); |
| 343 ruleFunctionSchemas.getRules.parameters); | 289 } |
| 290 getDeclarativeAPI().getRules( |
| 291 this.eventName_, ruleIdentifiers, cb); |
| 344 } | 292 } |
| 345 | 293 |
| 346 // Special load events: we don't use the DOM unload because that slows | 294 // Special load events: we don't use the DOM unload because that slows |
| 347 // down tab shutdown. On the other hand, onUnload might not always fire, | 295 // down tab shutdown. On the other hand, onUnload might not always fire, |
| 348 // since Chrome will terminate renderers on shutdown (SuddenTermination). | 296 // since Chrome will terminate renderers on shutdown (SuddenTermination). |
| 349 chromeHidden.onLoad = new chrome.Event(); | 297 chromeHidden.onLoad = new chrome.Event(); |
| 350 chromeHidden.onUnload = new chrome.Event(); | 298 chromeHidden.onUnload = new chrome.Event(); |
| 351 | 299 |
| 352 chromeHidden.dispatchOnLoad = | 300 chromeHidden.dispatchOnLoad = |
| 353 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); | 301 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); |
| 354 | 302 |
| 355 chromeHidden.dispatchOnUnload = function() { | 303 chromeHidden.dispatchOnUnload = function() { |
| 356 chromeHidden.onUnload.dispatch(); | 304 chromeHidden.onUnload.dispatch(); |
| 357 for (var i = 0; i < allAttachedEvents.length; ++i) { | 305 for (var i = 0; i < allAttachedEvents.length; ++i) { |
| 358 var event = allAttachedEvents[i]; | 306 var event = allAttachedEvents[i]; |
| 359 if (event) | 307 if (event) |
| 360 event.detach_(false); | 308 event.detach_(false); |
| 361 } | 309 } |
| 362 }; | 310 }; |
| 363 | 311 |
| 364 chromeHidden.dispatchError = function(msg) { | 312 chromeHidden.dispatchError = function(msg) { |
| 365 console.error(msg); | 313 console.error(msg); |
| 366 }; | 314 }; |
| 367 | 315 |
| 368 exports.Event = chrome.Event; | 316 exports.Event = chrome.Event; |
| OLD | NEW |