Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(439)

Side by Side Diff: chrome/renderer/resources/extensions/event.js

Issue 9192029: Bindings layer for declarative events API (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix some stuff that apitests in followup CL discovered Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698