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