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

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: Continued Created 8 years, 11 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();
11 //native function AddRules(rules);
12 //native function RemoveRules(rules);
Matt Perry 2012/01/24 22:39:10 dead code
battre 2012/01/25 19:25:08 Done.
11 13
12 var chromeHidden = GetChromeHidden(); 14 var chromeHidden = GetChromeHidden();
13 15
14 // Local implementation of JSON.parse & JSON.stringify that protect us 16 // Local implementation of JSON.parse & JSON.stringify that protect us
15 // from being clobbered by an extension. 17 // from being clobbered by an extension.
16 // 18 //
17 // TODO(aa): This makes me so sad. We shouldn't need it, as we can just pass 19 // TODO(aa): This makes me so sad. We shouldn't need it, as we can just pass
18 // Values directly over IPC without serializing to strings and use 20 // Values directly over IPC without serializing to strings and use
19 // JSONValueConverter. 21 // JSONValueConverter.
20 chromeHidden.JSON = new (function() { 22 chromeHidden.JSON = new (function() {
(...skipping 30 matching lines...) Expand all
51 53
52 // Event object. If opt_eventName is provided, this object represents 54 // Event object. If opt_eventName is provided, this object represents
53 // the unique instance of that named event, and dispatching an event 55 // the unique instance of that named event, and dispatching an event
54 // with that name will route through this object's listeners. 56 // with that name will route through this object's listeners.
55 // 57 //
56 // Example: 58 // Example:
57 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); 59 // chrome.tabs.onChanged = new chrome.Event("tab-changed");
58 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); 60 // chrome.tabs.onChanged.addListener(function(data) { alert(data); });
59 // chromeHidden.Event.dispatch("tab-changed", "hi"); 61 // chromeHidden.Event.dispatch("tab-changed", "hi");
60 // will result in an alert dialog that says 'hi'. 62 // will result in an alert dialog that says 'hi'.
61 chrome.Event = function(opt_eventName, opt_argSchemas) { 63 //
64 // If opt_eventOptions exists, it is a dictionary that contains the boolean
65 // entries "supportsListeners" and "supportsRules".
66 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions,
67 opt_typesAPI) {
62 this.eventName_ = opt_eventName; 68 this.eventName_ = opt_eventName;
63 this.listeners_ = []; 69 this.listeners_ = [];
70 this.eventOptions_ = opt_eventOptions ||
71 {"supportsListeners": true, "supportsRules": false};
72 if (opt_typesAPI) {
73 this.sendRequest_ = opt_typesAPI.sendRequest;
74 this.apiDefinitions_ = opt_typesAPI.apiDefinitions;
75 } else {
76 this.sendRequest_ = function() {};
77 this.apiDefinitions_ = {};
78 }
64 79
65 // Validate event parameters if we are in debug. 80 // Validate event parameters if we are in debug.
66 if (opt_argSchemas && 81 if (opt_argSchemas &&
67 chromeHidden.validateCallbacks && 82 chromeHidden.validateCallbacks &&
68 chromeHidden.validate) { 83 chromeHidden.validate) {
69 84
70 this.validate_ = function(args) { 85 this.validate_ = function(args) {
71 try { 86 try {
72 chromeHidden.validate(args, opt_argSchemas); 87 chromeHidden.validate(args, opt_argSchemas);
73 } catch (exception) { 88 } catch (exception) {
74 return "Event validation error during " + opt_eventName + " -- " + 89 return "Event validation error during " + opt_eventName + " -- " +
75 exception; 90 exception;
76 } 91 }
77 }; 92 };
93 } else {
94 this.validate_ = function() {}
78 } 95 }
96
97 this.rule_ids_ = {};
98 this.last_generated_rule_id_ = 0;
Matt Perry 2012/01/24 22:39:10 camelCase
battre 2012/01/25 19:25:08 Done.
79 }; 99 };
80 100
81 // A map of event names to the event object that is registered to that name. 101 // A map of event names to the event object that is registered to that name.
82 var attachedNamedEvents = {}; 102 var attachedNamedEvents = {};
83 103
84 // An array of all attached event objects, used for detaching on unload. 104 // An array of all attached event objects, used for detaching on unload.
85 var allAttachedEvents = []; 105 var allAttachedEvents = [];
86 106
87 // A map of functions that massage event arguments before they are dispatched. 107 // A map of functions that massage event arguments before they are dispatched.
88 // Key is event name, value is function. 108 // Key is event name, value is function.
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
120 }; 140 };
121 141
122 // Test if a named event has any listeners. 142 // Test if a named event has any listeners.
123 chromeHidden.Event.hasListener = function(name) { 143 chromeHidden.Event.hasListener = function(name) {
124 return (attachedNamedEvents[name] && 144 return (attachedNamedEvents[name] &&
125 attachedNamedEvents[name].listeners_.length > 0); 145 attachedNamedEvents[name].listeners_.length > 0);
126 }; 146 };
127 147
128 // Registers a callback to be called when this event is dispatched. 148 // Registers a callback to be called when this event is dispatched.
129 chrome.Event.prototype.addListener = function(cb) { 149 chrome.Event.prototype.addListener = function(cb) {
150 if (!this.eventOptions_.supportsListeners)
151 throw new Error("This event does not support listeners.");
130 if (this.listeners_.length == 0) { 152 if (this.listeners_.length == 0) {
131 this.attach_(); 153 this.attach_();
132 } 154 }
133 this.listeners_.push(cb); 155 this.listeners_.push(cb);
134 }; 156 };
135 157
136 // Unregisters a callback. 158 // Unregisters a callback.
137 chrome.Event.prototype.removeListener = function(cb) { 159 chrome.Event.prototype.removeListener = function(cb) {
160 if (!this.eventOptions_.supportsListeners)
161 throw new Error("This event does not support listeners.");
138 var idx = this.findListener_(cb); 162 var idx = this.findListener_(cb);
139 if (idx == -1) { 163 if (idx == -1) {
140 return; 164 return;
141 } 165 }
142 166
143 this.listeners_.splice(idx, 1); 167 this.listeners_.splice(idx, 1);
144 if (this.listeners_.length == 0) { 168 if (this.listeners_.length == 0) {
145 this.detach_(); 169 this.detach_();
146 } 170 }
147 }; 171 };
148 172
149 // Test if the given callback is registered for this event. 173 // Test if the given callback is registered for this event.
150 chrome.Event.prototype.hasListener = function(cb) { 174 chrome.Event.prototype.hasListener = function(cb) {
175 if (!this.eventOptions_.supportsListeners)
176 throw new Error("This event does not support listeners.");
151 return this.findListener_(cb) > -1; 177 return this.findListener_(cb) > -1;
152 }; 178 };
153 179
154 // Test if any callbacks are registered for this event. 180 // Test if any callbacks are registered for this event.
155 chrome.Event.prototype.hasListeners = function(cb) { 181 chrome.Event.prototype.hasListeners = function() {
182 if (!this.eventOptions_.supportsListeners)
183 throw new Error("This event does not support listeners.");
156 return this.listeners_.length > 0; 184 return this.listeners_.length > 0;
157 }; 185 };
158 186
159 // Returns the index of the given callback if registered, or -1 if not 187 // Returns the index of the given callback if registered, or -1 if not
160 // found. 188 // found.
161 chrome.Event.prototype.findListener_ = function(cb) { 189 chrome.Event.prototype.findListener_ = function(cb) {
162 for (var i = 0; i < this.listeners_.length; i++) { 190 for (var i = 0; i < this.listeners_.length; i++) {
163 if (this.listeners_[i] == cb) { 191 if (this.listeners_[i] == cb) {
164 return i; 192 return i;
165 } 193 }
166 } 194 }
167 195
168 return -1; 196 return -1;
169 }; 197 };
170 198
171 // Dispatches this event object to all listeners, passing all supplied 199 // Dispatches this event object to all listeners, passing all supplied
172 // arguments to this function each listener. 200 // arguments to this function each listener.
173 chrome.Event.prototype.dispatch = function(varargs) { 201 chrome.Event.prototype.dispatch = function(varargs) {
202 if (!this.eventOptions_.supportsListeners)
203 throw new Error("This event does not support listeners.");
174 var args = Array.prototype.slice.call(arguments); 204 var args = Array.prototype.slice.call(arguments);
175 if (this.validate_) { 205 if (this.validate_) {
176 var validationErrors = this.validate_(args); 206 var validationErrors = this.validate_(args);
177 if (validationErrors) { 207 if (validationErrors) {
178 return validationErrors; 208 return validationErrors;
179 } 209 }
180 } 210 }
181 for (var i = 0; i < this.listeners_.length; i++) { 211 for (var i = 0; i < this.listeners_.length; i++) {
182 try { 212 try {
183 this.listeners_[i].apply(null, args); 213 this.listeners_[i].apply(null, args);
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
220 250
221 delete attachedNamedEvents[this.eventName_]; 251 delete attachedNamedEvents[this.eventName_];
222 }; 252 };
223 253
224 chrome.Event.prototype.destroy_ = function() { 254 chrome.Event.prototype.destroy_ = function() {
225 this.listeners_ = []; 255 this.listeners_ = [];
226 this.validate_ = []; 256 this.validate_ = [];
227 this.detach_(); 257 this.detach_();
228 }; 258 };
229 259
260 chrome.Event.prototype.getFunctionDefinition_ =
261 function(namespace, functionName) {
262 var filterNamespace = function(val) {return val.namespace === namespace;};
263 var apiSchema = this.apiDefinitions_.filter(filterNamespace)[0];
264 var filterFunctionName = function (val) {return val.name === functionName;};
265 return apiSchema.functions.filter(filterFunctionName)[0];
266 }
267
268 // Takes a list of JSON datatype identifiers and returns a schema fragment
269 // that verifies that a JSON object corresponds to an array of only these
270 // data types.
271 chrome.Event.prototype.buildArrayOfChoicesSchema_ = function(typesList) {
272 return {
273 "type": "array",
274 "items": {
275 "choices": typesList.forEach(function(el) {return {"$ref": el};})
276 }
277 };
278 }
279
280 // Validate conditions and actions against specific schemas of this
281 // event object type.
282 // |rules| is an array of JSON objects that follow the Rule type of the
283 // declarative extension APIs. |conditions| is an array of JSON type
284 // identifiers that are allowed to occur in the conditions attribute of each
285 // rule. Likewise, |actions| is an array of JSON type identifiers that are
286 // allowed to occur in the actions attribute of each rule.
287 chrome.Event.prototype.validateRules_ = function(rules, conditions, actions) {
288 if (!conditions || !actions) {
289 throw new Error("Error in API specification.");
290 }
291 var conditionsSchema = this.buildArrayOfChoicesSchema_(conditions);
292 var actionsSchema = this.buildArrayOfChoicesSchema_(actions);
293 rules.forEach(function(rule) {
294 chromeHidden.validate([rule.conditions], [conditionsSchema]);
295 chromeHidden.validate([rule.actions], [actionsSchema]);
296 })
297 }
298
299 chrome.Event.prototype.addMissingIds_ = function(rules) {
300 for (var i = 0; i < rules.lengh; ++i) {
301 // TODO(battre): check for "".
302 if (!("id" in rule[i])) {
303 // Generate a unique ID.
304 var new_rule_id = "";
305 do {
306 new_rule_id = "_" + (this.last_generated_rule_id_++) + "_"
307 } while (new_rule_id in this.rule_ids_);
308 // And store it.
309 rule[i]["id"] = new_rule_id;
310 }
311 this.rule_ids_[rule[i]["id"]] = 1;
312 }
313 }
314
315 chrome.Event.prototype.addRules = function(rules, opt_cb) {
316 if (!this.eventOptions_.supportsRules)
317 throw new Error("This event does not support rules.");
318
319 this.validateRules_(rules,
320 this.eventOptions_.conditions,
321 this.eventOptions_.actions);
322 this.addMissingIds_(rules);
323 var callback = opt_cb ? opt_cb.bind(undefined, rules) : undefined;
324
325 var functionDef =
326 this.getFunctionDefinition_("experimental.declarative", "addRules");
327 return this.sendRequest_.call(this,
328 "experimental.declarative.addRules",
329 [this.eventName_, rules, callback],
330 functionDef.parameters);
331 }
332
333 chrome.Event.prototype.removeRules = function(rule_identifiers, opt_cb) {
334 if (!this.eventOptions_.supportsRules)
335 throw new Error("This event does not support rules.");
336 var functionDef =
337 this.getFunctionDefinition_("experimental.declarative", "removeRules");
338 console.log(JSON.stringify(functionDef));
Matt Perry 2012/01/24 22:39:10 remove debug code?
battre 2012/01/25 19:25:08 Done.
339 return this.sendRequest_.call(this,
340 "experimental.declarative.removeRules",
341 [this.eventName_, rule_identifiers, opt_cb],
342 functionDef.parameters);
343 }
344
345 chrome.Event.prototype.getRules = function(rule_identifiers, cb) {
346 if (!this.eventOptions_.supportsRules)
347 throw new Error("This event does not support rules.");
348 var functionDef =
349 this.getFunctionDefinition_("experimental.declarative", "getRules");
350 return this.sendRequest_.call(this,
351 "experimental.declarative.getRules",
352 [this.eventName_, rule_identifiers, cb],
353 functionDef.parameters);
354 }
355
230 // Special load events: we don't use the DOM unload because that slows 356 // 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, 357 // down tab shutdown. On the other hand, onUnload might not always fire,
232 // since Chrome will terminate renderers on shutdown (SuddenTermination). 358 // since Chrome will terminate renderers on shutdown (SuddenTermination).
233 chromeHidden.onLoad = new chrome.Event(); 359 chromeHidden.onLoad = new chrome.Event();
234 chromeHidden.onUnload = new chrome.Event(); 360 chromeHidden.onUnload = new chrome.Event();
235 361
236 chromeHidden.dispatchOnLoad = function(extensionId, isExtensionProcess, 362 chromeHidden.dispatchOnLoad = function(extensionId, isExtensionProcess,
237 isIncognitoContext) { 363 isIncognitoContext) {
238 chromeHidden.onLoad.dispatch(extensionId, isExtensionProcess, 364 chromeHidden.onLoad.dispatch(extensionId, isExtensionProcess,
239 isIncognitoContext); 365 isIncognitoContext);
240 }; 366 };
241 367
242 chromeHidden.dispatchOnUnload = function() { 368 chromeHidden.dispatchOnUnload = function() {
243 chromeHidden.onUnload.dispatch(); 369 chromeHidden.onUnload.dispatch();
244 for (var i = 0; i < allAttachedEvents.length; ++i) { 370 for (var i = 0; i < allAttachedEvents.length; ++i) {
245 var event = allAttachedEvents[i]; 371 var event = allAttachedEvents[i];
246 if (event) 372 if (event)
247 event.detach_(); 373 event.detach_();
374 if (event && event.eventOptions_.supportsRules)
375 event.removeRules([]);
248 } 376 }
249 }; 377 };
250 378
251 chromeHidden.dispatchError = function(msg) { 379 chromeHidden.dispatchError = function(msg) {
252 console.error(msg); 380 console.error(msg);
253 }; 381 };
254 })(); 382 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698