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

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: Store apiDefinitions directly in chromeHidden 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();
(...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) {
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
87 // A map of functions that massage event arguments before they are dispatched. 97 // A map of functions that massage event arguments before they are dispatched.
88 // Key is event name, value is function. 98 // Key is event name, value is function.
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
120 }; 130 };
121 131
122 // Test if a named event has any listeners. 132 // Test if a named event has any listeners.
123 chromeHidden.Event.hasListener = function(name) { 133 chromeHidden.Event.hasListener = function(name) {
124 return (attachedNamedEvents[name] && 134 return (attachedNamedEvents[name] &&
125 attachedNamedEvents[name].listeners_.length > 0); 135 attachedNamedEvents[name].listeners_.length > 0);
126 }; 136 };
127 137
128 // Registers a callback to be called when this event is dispatched. 138 // Registers a callback to be called when this event is dispatched.
129 chrome.Event.prototype.addListener = function(cb) { 139 chrome.Event.prototype.addListener = function(cb) {
140 if (!this.eventOptions_.supportsListeners)
141 throw new Error("This event does not support listeners.");
130 if (this.listeners_.length == 0) { 142 if (this.listeners_.length == 0) {
131 this.attach_(); 143 this.attach_();
132 } 144 }
133 this.listeners_.push(cb); 145 this.listeners_.push(cb);
134 }; 146 };
135 147
136 // Unregisters a callback. 148 // Unregisters a callback.
137 chrome.Event.prototype.removeListener = function(cb) { 149 chrome.Event.prototype.removeListener = function(cb) {
150 if (!this.eventOptions_.supportsListeners)
151 throw new Error("This event does not support listeners.");
138 var idx = this.findListener_(cb); 152 var idx = this.findListener_(cb);
139 if (idx == -1) { 153 if (idx == -1) {
140 return; 154 return;
141 } 155 }
142 156
143 this.listeners_.splice(idx, 1); 157 this.listeners_.splice(idx, 1);
144 if (this.listeners_.length == 0) { 158 if (this.listeners_.length == 0) {
145 this.detach_(); 159 this.detach_();
146 } 160 }
147 }; 161 };
148 162
149 // Test if the given callback is registered for this event. 163 // Test if the given callback is registered for this event.
150 chrome.Event.prototype.hasListener = function(cb) { 164 chrome.Event.prototype.hasListener = function(cb) {
165 if (!this.eventOptions_.supportsListeners)
166 throw new Error("This event does not support listeners.");
151 return this.findListener_(cb) > -1; 167 return this.findListener_(cb) > -1;
152 }; 168 };
153 169
154 // Test if any callbacks are registered for this event. 170 // Test if any callbacks are registered for this event.
155 chrome.Event.prototype.hasListeners = function(cb) { 171 chrome.Event.prototype.hasListeners = function() {
172 if (!this.eventOptions_.supportsListeners)
173 throw new Error("This event does not support listeners.");
156 return this.listeners_.length > 0; 174 return this.listeners_.length > 0;
157 }; 175 };
158 176
159 // Returns the index of the given callback if registered, or -1 if not 177 // Returns the index of the given callback if registered, or -1 if not
160 // found. 178 // found.
161 chrome.Event.prototype.findListener_ = function(cb) { 179 chrome.Event.prototype.findListener_ = function(cb) {
162 for (var i = 0; i < this.listeners_.length; i++) { 180 for (var i = 0; i < this.listeners_.length; i++) {
163 if (this.listeners_[i] == cb) { 181 if (this.listeners_[i] == cb) {
164 return i; 182 return i;
165 } 183 }
166 } 184 }
167 185
168 return -1; 186 return -1;
169 }; 187 };
170 188
171 // Dispatches this event object to all listeners, passing all supplied 189 // Dispatches this event object to all listeners, passing all supplied
172 // arguments to this function each listener. 190 // arguments to this function each listener.
173 chrome.Event.prototype.dispatch = function(varargs) { 191 chrome.Event.prototype.dispatch = function(varargs) {
192 if (!this.eventOptions_.supportsListeners)
193 throw new Error("This event does not support listeners.");
174 var args = Array.prototype.slice.call(arguments); 194 var args = Array.prototype.slice.call(arguments);
175 if (this.validate_) { 195 if (this.validate_) {
176 var validationErrors = this.validate_(args); 196 var validationErrors = this.validate_(args);
177 if (validationErrors) { 197 if (validationErrors) {
178 return validationErrors; 198 return validationErrors;
179 } 199 }
180 } 200 }
181 for (var i = 0; i < this.listeners_.length; i++) { 201 for (var i = 0; i < this.listeners_.length; i++) {
182 try { 202 try {
183 this.listeners_[i].apply(null, args); 203 this.listeners_[i].apply(null, args);
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
220 240
221 delete attachedNamedEvents[this.eventName_]; 241 delete attachedNamedEvents[this.eventName_];
222 }; 242 };
223 243
224 chrome.Event.prototype.destroy_ = function() { 244 chrome.Event.prototype.destroy_ = function() {
225 this.listeners_ = []; 245 this.listeners_ = [];
226 this.validate_ = []; 246 this.validate_ = [];
227 this.detach_(); 247 this.detach_();
228 }; 248 };
229 249
250 chrome.Event.prototype.getFunctionDefinition_ =
251 function(namespace, functionName) {
252 var filterNamespace = function(val) {return val.namespace === namespace;};
253 var apiSchema = chromeHidden.apiDefinitions.filter(filterNamespace)[0];
254 var filterFunctionName = function (val) {return val.name === functionName;};
255 return apiSchema.functions.filter(filterFunctionName)[0];
256 }
257
258 // Takes a list of JSON datatype identifiers and returns a schema fragment
259 // that verifies that a JSON object corresponds to an array of only these
260 // data types.
261 chrome.Event.prototype.buildArrayOfChoicesSchema_ = function(typesList) {
262 return {
263 "type": "array",
264 "items": {
265 "choices": typesList.forEach(function(el) {return {"$ref": el};})
266 }
267 };
268 }
269
270 // Validate conditions and actions against specific schemas of this
271 // event object type.
272 // |rules| is an array of JSON objects that follow the Rule type of the
273 // declarative extension APIs. |conditions| is an array of JSON type
274 // identifiers that are allowed to occur in the conditions attribute of each
275 // rule. Likewise, |actions| is an array of JSON type identifiers that are
276 // allowed to occur in the actions attribute of each rule.
277 chrome.Event.prototype.validateRules_ = function(rules, conditions, actions) {
278 if (!conditions || !actions) {
279 throw new Error("Error in API specification.");
280 }
281 var conditionsSchema = this.buildArrayOfChoicesSchema_(conditions);
282 var actionsSchema = this.buildArrayOfChoicesSchema_(actions);
283 rules.forEach(function(rule) {
284 chromeHidden.validate([rule.conditions], [conditionsSchema]);
285 chromeHidden.validate([rule.actions], [actionsSchema]);
286 })
287 }
288
289 chrome.Event.prototype.addMissingIds_ = function(rules) {
290 for (var i = 0; i < rules.lengh; ++i) {
291 // TODO(battre): check for "".
292 if (!("id" in rule[i])) {
293 // Generate a unique ID.
294 var newRuleId = "";
295 do {
296 newRuleId = "_" + (this.lastGeneratedRuleId_++) + "_"
297 } while (newRuleId in this.ruleIds_);
298 // And store it.
299 rule[i]["id"] = newRuleId;
300 }
301 this.ruleIds_[rule[i]["id"]] = 1;
302 }
303 }
304
305 chrome.Event.prototype.addRules = function(rules, opt_cb) {
306 if (!this.eventOptions_.supportsRules)
307 throw new Error("This event does not support rules.");
308
309 this.validateRules_(rules,
310 this.eventOptions_.conditions,
311 this.eventOptions_.actions);
312 this.addMissingIds_(rules);
313 var callback = opt_cb ? opt_cb.bind(undefined, rules) : undefined;
314
315 var functionDef =
316 this.getFunctionDefinition_("experimental.declarative", "addRules");
Aaron Boodman 2012/01/31 00:11:17 This seems like something sendRequest (or some var
battre 2012/01/31 12:28:50 Done.
317 return chromeHidden.sendRequest.call(
318 this,
319 "experimental.declarative.addRules",
320 [this.eventName_, rules, callback],
321 functionDef.parameters);
322 }
323
324 chrome.Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) {
325 if (!this.eventOptions_.supportsRules)
326 throw new Error("This event does not support rules.");
327 var functionDef =
328 this.getFunctionDefinition_("experimental.declarative", "removeRules");
329 return chromeHidden.sendRequest.call(
330 this,
331 "experimental.declarative.removeRules",
332 [this.eventName_, ruleIdentifiers, opt_cb],
333 functionDef.parameters);
334 }
335
336 chrome.Event.prototype.getRules = function(ruleIdentifiers, cb) {
337 if (!this.eventOptions_.supportsRules)
338 throw new Error("This event does not support rules.");
339 var functionDef =
340 this.getFunctionDefinition_("experimental.declarative", "getRules");
341 return chromeHidden.sendRequest.call(
342 this,
343 "experimental.declarative.getRules",
344 [this.eventName_, ruleIdentifiers, cb],
345 functionDef.parameters);
346 }
347
230 // Special load events: we don't use the DOM unload because that slows 348 // 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, 349 // down tab shutdown. On the other hand, onUnload might not always fire,
232 // since Chrome will terminate renderers on shutdown (SuddenTermination). 350 // since Chrome will terminate renderers on shutdown (SuddenTermination).
233 chromeHidden.onLoad = new chrome.Event(); 351 chromeHidden.onLoad = new chrome.Event();
234 chromeHidden.onUnload = new chrome.Event(); 352 chromeHidden.onUnload = new chrome.Event();
235 353
236 chromeHidden.dispatchOnLoad = function(extensionId, isExtensionProcess, 354 chromeHidden.dispatchOnLoad = function(extensionId, isExtensionProcess,
237 isIncognitoContext) { 355 isIncognitoContext) {
238 chromeHidden.onLoad.dispatch(extensionId, isExtensionProcess, 356 chromeHidden.onLoad.dispatch(extensionId, isExtensionProcess,
239 isIncognitoContext); 357 isIncognitoContext);
240 }; 358 };
241 359
242 chromeHidden.dispatchOnUnload = function() { 360 chromeHidden.dispatchOnUnload = function() {
243 chromeHidden.onUnload.dispatch(); 361 chromeHidden.onUnload.dispatch();
244 for (var i = 0; i < allAttachedEvents.length; ++i) { 362 for (var i = 0; i < allAttachedEvents.length; ++i) {
245 var event = allAttachedEvents[i]; 363 var event = allAttachedEvents[i];
246 if (event) 364 if (event)
247 event.detach_(); 365 event.detach_();
366 if (event && event.eventOptions_.supportsRules)
367 event.removeRules([]);
248 } 368 }
249 }; 369 };
250 370
251 chromeHidden.dispatchError = function(msg) { 371 chromeHidden.dispatchError = function(msg) {
252 console.error(msg); 372 console.error(msg);
253 }; 373 };
254 })(); 374 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698