Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 chrome = chrome || {}; | 5 var chrome = chrome || {}; |
| 6 (function () { | 6 (function () { |
| 7 native function GetChromeHidden(); | 7 native function GetChromeHidden(); |
| 8 native function AttachEvent(eventName); | 8 native function AttachEvent(eventName); |
| 9 native function DetachEvent(eventName); | 9 native function DetachEvent(eventName); |
| 10 native function Print(); | 10 native function Print(); |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 51 | 51 |
| 52 // Event object. If opt_eventName is provided, this object represents | 52 // Event object. If opt_eventName is provided, this object represents |
| 53 // the unique instance of that named event, and dispatching an event | 53 // the unique instance of that named event, and dispatching an event |
| 54 // with that name will route through this object's listeners. | 54 // with that name will route through this object's listeners. |
| 55 // | 55 // |
| 56 // Example: | 56 // Example: |
| 57 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); | 57 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); |
| 58 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); | 58 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); |
| 59 // chromeHidden.Event.dispatch("tab-changed", "hi"); | 59 // chromeHidden.Event.dispatch("tab-changed", "hi"); |
| 60 // will result in an alert dialog that says 'hi'. | 60 // will result in an alert dialog that says 'hi'. |
| 61 chrome.Event = function(opt_eventName, opt_argSchemas) { | 61 // |
| 62 // If opt_eventOptions exists, it is a dictionary that contains the boolean | |
| 63 // entries "supportsListeners" and "supportsRules". | |
| 64 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { | |
|
not at google - send to devlin
2012/02/01 07:23:12
eventCapabilities might be a better name?
battre
2012/02/01 17:35:39
Even if we continue adding stuff, such in this exa
not at google - send to devlin
2012/02/01 23:07:23
Yep, never mind. They're also defined as "options"
| |
| 62 this.eventName_ = opt_eventName; | 65 this.eventName_ = opt_eventName; |
| 63 this.listeners_ = []; | 66 this.listeners_ = []; |
| 67 this.eventOptions_ = opt_eventOptions || | |
| 68 {"supportsListeners": true, "supportsRules": false}; | |
| 64 | 69 |
| 65 // Validate event parameters if we are in debug. | 70 // Validate event parameters if we are in debug. |
| 66 if (opt_argSchemas && | 71 if (opt_argSchemas && |
| 67 chromeHidden.validateCallbacks && | 72 chromeHidden.validateCallbacks && |
| 68 chromeHidden.validate) { | 73 chromeHidden.validate) { |
| 69 | 74 |
| 70 this.validate_ = function(args) { | 75 this.validate_ = function(args) { |
| 71 try { | 76 try { |
| 72 chromeHidden.validate(args, opt_argSchemas); | 77 chromeHidden.validate(args, opt_argSchemas); |
| 73 } catch (exception) { | 78 } catch (exception) { |
| 74 return "Event validation error during " + opt_eventName + " -- " + | 79 return "Event validation error during " + opt_eventName + " -- " + |
| 75 exception; | 80 exception; |
| 76 } | 81 } |
| 77 }; | 82 }; |
| 83 } else { | |
| 84 this.validate_ = function() {} | |
| 78 } | 85 } |
| 86 | |
| 87 this.ruleIds_ = {}; | |
| 88 this.lastGeneratedRuleId_ = 0; | |
| 79 }; | 89 }; |
| 80 | 90 |
| 81 // A map of event names to the event object that is registered to that name. | 91 // A map of event names to the event object that is registered to that name. |
| 82 var attachedNamedEvents = {}; | 92 var attachedNamedEvents = {}; |
| 83 | 93 |
| 84 // An array of all attached event objects, used for detaching on unload. | 94 // An array of all attached event objects, used for detaching on unload. |
| 85 var allAttachedEvents = []; | 95 var allAttachedEvents = []; |
| 86 | 96 |
| 97 // An array of all events that have had rules added the them at any time. | |
| 98 var allEventsWithRules = []; | |
| 99 | |
| 87 // A map of functions that massage event arguments before they are dispatched. | 100 // A map of functions that massage event arguments before they are dispatched. |
| 88 // Key is event name, value is function. | 101 // Key is event name, value is function. |
| 89 var eventArgumentMassagers = {}; | 102 var eventArgumentMassagers = {}; |
| 90 | 103 |
| 91 chromeHidden.Event = {}; | 104 chromeHidden.Event = {}; |
| 92 | 105 |
| 93 chromeHidden.Event.registerArgumentMassager = function(name, fn) { | 106 chromeHidden.Event.registerArgumentMassager = function(name, fn) { |
| 94 if (eventArgumentMassagers[name]) | 107 if (eventArgumentMassagers[name]) |
| 95 throw new Error("Massager already registered for event: " + name); | 108 throw new Error("Massager already registered for event: " + name); |
| 96 eventArgumentMassagers[name] = fn; | 109 eventArgumentMassagers[name] = fn; |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 120 }; | 133 }; |
| 121 | 134 |
| 122 // Test if a named event has any listeners. | 135 // Test if a named event has any listeners. |
| 123 chromeHidden.Event.hasListener = function(name) { | 136 chromeHidden.Event.hasListener = function(name) { |
| 124 return (attachedNamedEvents[name] && | 137 return (attachedNamedEvents[name] && |
| 125 attachedNamedEvents[name].listeners_.length > 0); | 138 attachedNamedEvents[name].listeners_.length > 0); |
| 126 }; | 139 }; |
| 127 | 140 |
| 128 // Registers a callback to be called when this event is dispatched. | 141 // Registers a callback to be called when this event is dispatched. |
| 129 chrome.Event.prototype.addListener = function(cb) { | 142 chrome.Event.prototype.addListener = function(cb) { |
| 143 if (!this.eventOptions_.supportsListeners) | |
| 144 throw new Error("This event does not support listeners."); | |
| 130 if (this.listeners_.length == 0) { | 145 if (this.listeners_.length == 0) { |
| 131 this.attach_(); | 146 this.attach_(); |
| 132 } | 147 } |
| 133 this.listeners_.push(cb); | 148 this.listeners_.push(cb); |
| 134 }; | 149 }; |
| 135 | 150 |
| 136 // Unregisters a callback. | 151 // Unregisters a callback. |
| 137 chrome.Event.prototype.removeListener = function(cb) { | 152 chrome.Event.prototype.removeListener = function(cb) { |
| 153 if (!this.eventOptions_.supportsListeners) | |
| 154 throw new Error("This event does not support listeners."); | |
| 138 var idx = this.findListener_(cb); | 155 var idx = this.findListener_(cb); |
| 139 if (idx == -1) { | 156 if (idx == -1) { |
| 140 return; | 157 return; |
| 141 } | 158 } |
| 142 | 159 |
| 143 this.listeners_.splice(idx, 1); | 160 this.listeners_.splice(idx, 1); |
| 144 if (this.listeners_.length == 0) { | 161 if (this.listeners_.length == 0) { |
| 145 this.detach_(); | 162 this.detach_(); |
| 146 } | 163 } |
| 147 }; | 164 }; |
| 148 | 165 |
| 149 // Test if the given callback is registered for this event. | 166 // Test if the given callback is registered for this event. |
| 150 chrome.Event.prototype.hasListener = function(cb) { | 167 chrome.Event.prototype.hasListener = function(cb) { |
| 168 if (!this.eventOptions_.supportsListeners) | |
| 169 throw new Error("This event does not support listeners."); | |
| 151 return this.findListener_(cb) > -1; | 170 return this.findListener_(cb) > -1; |
| 152 }; | 171 }; |
| 153 | 172 |
| 154 // Test if any callbacks are registered for this event. | 173 // Test if any callbacks are registered for this event. |
| 155 chrome.Event.prototype.hasListeners = function(cb) { | 174 chrome.Event.prototype.hasListeners = function() { |
| 175 if (!this.eventOptions_.supportsListeners) | |
| 176 throw new Error("This event does not support listeners."); | |
| 156 return this.listeners_.length > 0; | 177 return this.listeners_.length > 0; |
| 157 }; | 178 }; |
| 158 | 179 |
| 159 // Returns the index of the given callback if registered, or -1 if not | 180 // Returns the index of the given callback if registered, or -1 if not |
| 160 // found. | 181 // found. |
| 161 chrome.Event.prototype.findListener_ = function(cb) { | 182 chrome.Event.prototype.findListener_ = function(cb) { |
| 162 for (var i = 0; i < this.listeners_.length; i++) { | 183 for (var i = 0; i < this.listeners_.length; i++) { |
| 163 if (this.listeners_[i] == cb) { | 184 if (this.listeners_[i] == cb) { |
| 164 return i; | 185 return i; |
| 165 } | 186 } |
| 166 } | 187 } |
| 167 | 188 |
| 168 return -1; | 189 return -1; |
| 169 }; | 190 }; |
| 170 | 191 |
| 171 // Dispatches this event object to all listeners, passing all supplied | 192 // Dispatches this event object to all listeners, passing all supplied |
| 172 // arguments to this function each listener. | 193 // arguments to this function each listener. |
| 173 chrome.Event.prototype.dispatch = function(varargs) { | 194 chrome.Event.prototype.dispatch = function(varargs) { |
| 195 if (!this.eventOptions_.supportsListeners) | |
| 196 throw new Error("This event does not support listeners."); | |
| 174 var args = Array.prototype.slice.call(arguments); | 197 var args = Array.prototype.slice.call(arguments); |
| 175 if (this.validate_) { | 198 if (this.validate_) { |
|
not at google - send to devlin
2012/02/01 07:23:12
now that there's always set a validate_ method, th
| |
| 176 var validationErrors = this.validate_(args); | 199 var validationErrors = this.validate_(args); |
| 177 if (validationErrors) { | 200 if (validationErrors) { |
| 178 return validationErrors; | 201 return validationErrors; |
| 179 } | 202 } |
| 180 } | 203 } |
| 181 for (var i = 0; i < this.listeners_.length; i++) { | 204 for (var i = 0; i < this.listeners_.length; i++) { |
| 182 try { | 205 try { |
| 183 this.listeners_[i].apply(null, args); | 206 this.listeners_[i].apply(null, args); |
| 184 } catch (e) { | 207 } catch (e) { |
| 185 console.error("Error in event handler for '" + this.eventName_ + | 208 console.error("Error in event handler for '" + this.eventName_ + |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 220 | 243 |
| 221 delete attachedNamedEvents[this.eventName_]; | 244 delete attachedNamedEvents[this.eventName_]; |
| 222 }; | 245 }; |
| 223 | 246 |
| 224 chrome.Event.prototype.destroy_ = function() { | 247 chrome.Event.prototype.destroy_ = function() { |
| 225 this.listeners_ = []; | 248 this.listeners_ = []; |
| 226 this.validate_ = []; | 249 this.validate_ = []; |
| 227 this.detach_(); | 250 this.detach_(); |
| 228 }; | 251 }; |
| 229 | 252 |
| 253 // Takes a list of JSON datatype identifiers and returns a schema fragment | |
| 254 // that verifies that a JSON object corresponds to an array of only these | |
| 255 // data types. | |
| 256 chrome.Event.prototype.buildArrayOfChoicesSchema_ = function(typesList) { | |
| 257 return { | |
| 258 "type": "array", | |
| 259 "items": { | |
| 260 "choices": typesList.forEach(function(el) {return {"$ref": el};}) | |
|
not at google - send to devlin
2012/02/01 08:15:57
What does this do? I thought forEach has no return
battre
2012/02/01 17:35:39
This should have been "map" instead of "forEach".
| |
| 261 } | |
| 262 }; | |
| 263 } | |
| 264 | |
| 265 // Validate conditions and actions against specific schemas of this | |
| 266 // event object type. | |
| 267 // |rules| is an array of JSON objects that follow the Rule type of the | |
| 268 // declarative extension APIs. |conditions| is an array of JSON type | |
| 269 // identifiers that are allowed to occur in the conditions attribute of each | |
| 270 // rule. Likewise, |actions| is an array of JSON type identifiers that are | |
| 271 // allowed to occur in the actions attribute of each rule. | |
| 272 chrome.Event.prototype.validateRules_ = function(rules, conditions, actions) { | |
| 273 if (!conditions || !actions) { | |
| 274 throw new Error("Error in API specification."); | |
| 275 } | |
| 276 var conditionsSchema = this.buildArrayOfChoicesSchema_(conditions); | |
| 277 var actionsSchema = this.buildArrayOfChoicesSchema_(actions); | |
| 278 rules.forEach(function(rule) { | |
| 279 chromeHidden.validate([rule.conditions], [conditionsSchema]); | |
| 280 chromeHidden.validate([rule.actions], [actionsSchema]); | |
| 281 }) | |
| 282 } | |
| 283 | |
| 284 chrome.Event.prototype.addMissingIds_ = function(rules) { | |
|
not at google - send to devlin
2012/02/01 07:23:12
This, and addMissingPriorities, should be done in
battre
2012/02/01 17:35:39
This does not work:
We want to tell the extension
| |
| 285 for (var i = 0; i < rules.length; ++i) { | |
| 286 // TODO(battre): check for "". | |
| 287 if (!("id" in rules[i])) { | |
| 288 // Generate a unique ID. | |
| 289 var newRuleId = ""; | |
| 290 do { | |
| 291 newRuleId = "_" + (this.lastGeneratedRuleId_++) + "_" | |
| 292 } while (newRuleId in this.ruleIds_); | |
| 293 // And store it. | |
| 294 rules[i]["id"] = newRuleId; | |
| 295 } | |
| 296 this.ruleIds_[rules[i]["id"]] = 1; | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 chrome.Event.prototype.addMissingPriorities_ = function(rules) { | |
| 301 for (var i = 0; i < rules.length; ++i) { | |
| 302 if (!("priority" in rules[i])) | |
| 303 rules[i]["priority"] = 100; | |
| 304 } | |
| 305 } | |
| 306 | |
| 307 chrome.Event.prototype.addRules = function(rules, opt_cb) { | |
|
not at google - send to devlin
2012/02/01 07:23:12
I think that this should just be a thin wrapper ar
battre
2012/02/01 17:35:39
Isn't it a pretty thin wrapper? Which part would y
not at google - send to devlin
2012/02/01 23:07:23
Yep, it is now. By "thin wrapper" I meant... what
| |
| 308 if (!this.eventOptions_.supportsRules) | |
| 309 throw new Error("This event does not support rules."); | |
| 310 | |
| 311 var found = false; | |
| 312 for (var i = 0; i < allEventsWithRules.length; ++i) { | |
| 313 if (allEventsWithRules === this) { | |
| 314 found = true; | |
| 315 break; | |
| 316 } | |
| 317 } | |
| 318 if (!found) | |
| 319 allEventsWithRules.push(this); | |
|
not at google - send to devlin
2012/02/01 07:23:12
This implies an O(n) iteration over all events eac
battre
2012/02/01 17:35:39
I have removed this logic, see below.
| |
| 320 | |
| 321 this.validateRules_(rules, | |
|
not at google - send to devlin
2012/02/01 07:23:12
I would have thought that the schema-based validat
not at google - send to devlin
2012/02/01 08:15:57
I may have gotten my wires a little bit crossed wh
battre
2012/02/01 17:35:39
I am not sure how how we will implement these cons
| |
| 322 this.eventOptions_.conditions, | |
| 323 this.eventOptions_.actions); | |
| 324 this.addMissingIds_(rules); | |
| 325 this.addMissingPriorities_(rules); | |
| 326 var callback = opt_cb ? opt_cb.bind(undefined, rules) : undefined; | |
| 327 | |
| 328 return chromeHidden.validatingSendRequest.call( | |
|
not at google - send to devlin
2012/02/01 07:23:12
This is interesting. All you really need to do, at
battre
2012/02/01 17:35:39
Wow, this became significantly simpler.
Done.
| |
| 329 this, | |
| 330 "experimental.declarative.addRules", | |
| 331 [this.eventName_, rules, callback]); | |
| 332 } | |
| 333 | |
| 334 chrome.Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { | |
| 335 if (!this.eventOptions_.supportsRules) | |
| 336 throw new Error("This event does not support rules."); | |
| 337 return chromeHidden.validatingSendRequest.call( | |
| 338 this, | |
| 339 "experimental.declarative.removeRules", | |
| 340 [this.eventName_, ruleIdentifiers, opt_cb]); | |
| 341 } | |
| 342 | |
| 343 chrome.Event.prototype.getRules = function(ruleIdentifiers, cb) { | |
| 344 if (!this.eventOptions_.supportsRules) | |
| 345 throw new Error("This event does not support rules."); | |
| 346 return chromeHidden.validatingSendRequest.call( | |
| 347 this, | |
| 348 "experimental.declarative.getRules", | |
| 349 [this.eventName_, ruleIdentifiers, cb]); | |
| 350 } | |
| 351 | |
| 230 // Special load events: we don't use the DOM unload because that slows | 352 // Special load events: we don't use the DOM unload because that slows |
| 231 // down tab shutdown. On the other hand, onUnload might not always fire, | 353 // down tab shutdown. On the other hand, onUnload might not always fire, |
| 232 // since Chrome will terminate renderers on shutdown (SuddenTermination). | 354 // since Chrome will terminate renderers on shutdown (SuddenTermination). |
| 233 chromeHidden.onLoad = new chrome.Event(); | 355 chromeHidden.onLoad = new chrome.Event(); |
| 234 chromeHidden.onUnload = new chrome.Event(); | 356 chromeHidden.onUnload = new chrome.Event(); |
| 235 | 357 |
| 236 chromeHidden.dispatchOnLoad = function(extensionId, isExtensionProcess, | 358 chromeHidden.dispatchOnLoad = function(extensionId, isExtensionProcess, |
| 237 isIncognitoContext) { | 359 isIncognitoContext) { |
| 238 chromeHidden.onLoad.dispatch(extensionId, isExtensionProcess, | 360 chromeHidden.onLoad.dispatch(extensionId, isExtensionProcess, |
| 239 isIncognitoContext); | 361 isIncognitoContext); |
| 240 }; | 362 }; |
| 241 | 363 |
| 242 chromeHidden.dispatchOnUnload = function() { | 364 chromeHidden.dispatchOnUnload = function() { |
| 243 chromeHidden.onUnload.dispatch(); | 365 chromeHidden.onUnload.dispatch(); |
| 244 for (var i = 0; i < allAttachedEvents.length; ++i) { | 366 for (var i = 0; i < allAttachedEvents.length; ++i) { |
| 245 var event = allAttachedEvents[i]; | 367 var event = allAttachedEvents[i]; |
|
not at google - send to devlin
2012/02/01 07:23:12
I.e. with the above suggestion, this would be
var
battre
2012/02/01 17:35:39
Solved, see below.
| |
| 246 if (event) | 368 if (event) |
| 247 event.detach_(); | 369 event.detach_(); |
| 248 } | 370 } |
| 371 for (var i = 0; i < allEventsWithRules.length; ++i) { | |
| 372 var event = allEventsWithRules[i]; | |
| 373 if (event && event.eventOptions_.supportsRules) | |
| 374 event.removeRules([]); | |
|
not at google - send to devlin
2012/02/01 07:23:12
Why [] ? Is this to signify removing all rules?
T
battre
2012/02/01 17:35:39
I have removed this logic because
a) it did not wo
| |
| 375 } | |
| 249 }; | 376 }; |
| 250 | 377 |
| 251 chromeHidden.dispatchError = function(msg) { | 378 chromeHidden.dispatchError = function(msg) { |
| 252 console.error(msg); | 379 console.error(msg); |
| 253 }; | 380 }; |
| 254 })(); | 381 })(); |
| OLD | NEW |