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

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

Issue 11571014: Lazy load chrome.* APIs (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: addressed comments Created 7 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) 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 DCHECK = requireNative('logging').DCHECK; 5 var DCHECK = requireNative('logging').DCHECK;
6 require('json_schema');
not at google - send to devlin 2013/02/15 22:26:17 TODO: json_schema shouldn't put things on chromeHi
cduvall 2013/02/19 23:58:49 Done.
6 var eventBindingsNatives = requireNative('event_bindings'); 7 var eventBindingsNatives = requireNative('event_bindings');
7 var AttachEvent = eventBindingsNatives.AttachEvent; 8 var AttachEvent = eventBindingsNatives.AttachEvent;
8 var DetachEvent = eventBindingsNatives.DetachEvent; 9 var DetachEvent = eventBindingsNatives.DetachEvent;
9 var AttachFilteredEvent = eventBindingsNatives.AttachFilteredEvent; 10 var AttachFilteredEvent = eventBindingsNatives.AttachFilteredEvent;
10 var DetachFilteredEvent = eventBindingsNatives.DetachFilteredEvent; 11 var DetachFilteredEvent = eventBindingsNatives.DetachFilteredEvent;
11 var MatchAgainstEventFilter = eventBindingsNatives.MatchAgainstEventFilter; 12 var MatchAgainstEventFilter = eventBindingsNatives.MatchAgainstEventFilter;
12 var sendRequest = require('sendRequest').sendRequest; 13 var sendRequest = require('sendRequest').sendRequest;
13 var utils = require('utils'); 14 var utils = require('utils');
14 var validate = require('schemaUtils').validate; 15 var validate = require('schemaUtils').validate;
15 16
16 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); 17 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden();
17 var GetExtensionAPIDefinition = 18 var chrome = requireNative('chrome').GetChrome();
18 requireNative('apiDefinitions').GetExtensionAPIDefinition; 19 var schemaRegistry = requireNative('schema_registry');
19 20
20 // Schemas for the rule-style functions on the events API that 21 // Schemas for the rule-style functions on the events API that
21 // only need to be generated occasionally, so populate them lazily. 22 // only need to be generated occasionally, so populate them lazily.
22 var ruleFunctionSchemas = { 23 var ruleFunctionSchemas = {
23 // These values are set lazily: 24 // These values are set lazily:
24 // addRules: {}, 25 // addRules: {},
25 // getRules: {}, 26 // getRules: {},
26 // removeRules: {} 27 // removeRules: {}
27 }; 28 };
28 29
29 // This function ensures that |ruleFunctionSchemas| is populated. 30 // This function ensures that |ruleFunctionSchemas| is populated.
30 function ensureRuleSchemasLoaded() { 31 function ensureRuleSchemasLoaded() {
31 if (ruleFunctionSchemas.addRules) 32 if (ruleFunctionSchemas.addRules)
32 return; 33 return;
33 var eventsSchema = GetExtensionAPIDefinition("events")[0]; 34 var eventsSchema = schemaRegistry.GetSchema("events");
34 var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event'); 35 var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event');
35 36
36 ruleFunctionSchemas.addRules = 37 ruleFunctionSchemas.addRules =
37 utils.lookup(eventType.functions, 'name', 'addRules'); 38 utils.lookup(eventType.functions, 'name', 'addRules');
38 ruleFunctionSchemas.getRules = 39 ruleFunctionSchemas.getRules =
39 utils.lookup(eventType.functions, 'name', 'getRules'); 40 utils.lookup(eventType.functions, 'name', 'getRules');
40 ruleFunctionSchemas.removeRules = 41 ruleFunctionSchemas.removeRules =
41 utils.lookup(eventType.functions, 'name', 'removeRules'); 42 utils.lookup(eventType.functions, 'name', 'removeRules');
42 } 43 }
43 44
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after
182 // opt_eventName is required for events that support rules. 183 // opt_eventName is required for events that support rules.
183 // 184 //
184 // Example: 185 // Example:
185 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); 186 // chrome.tabs.onChanged = new chrome.Event("tab-changed");
186 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); 187 // chrome.tabs.onChanged.addListener(function(data) { alert(data); });
187 // chromeHidden.Event.dispatch("tab-changed", "hi"); 188 // chromeHidden.Event.dispatch("tab-changed", "hi");
188 // will result in an alert dialog that says 'hi'. 189 // will result in an alert dialog that says 'hi'.
189 // 190 //
190 // If opt_eventOptions exists, it is a dictionary that contains the boolean 191 // If opt_eventOptions exists, it is a dictionary that contains the boolean
191 // entries "supportsListeners" and "supportsRules". 192 // entries "supportsListeners" and "supportsRules".
192 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { 193 var Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) {
193 this.eventName_ = opt_eventName; 194 this.eventName_ = opt_eventName;
194 this.listeners_ = []; 195 this.listeners_ = [];
195 this.eventOptions_ = chromeHidden.parseEventOptions(opt_eventOptions); 196 this.eventOptions_ = chromeHidden.parseEventOptions(opt_eventOptions);
196 197
197 if (this.eventOptions_.supportsRules && !opt_eventName) 198 if (this.eventOptions_.supportsRules && !opt_eventName)
198 throw new Error("Events that support rules require an event name."); 199 throw new Error("Events that support rules require an event name.");
199 200
200 if (this.eventOptions_.supportsFilters) { 201 if (this.eventOptions_.supportsFilters) {
201 this.attachmentStrategy_ = new FilteredAttachmentStrategy(this); 202 this.attachmentStrategy_ = new FilteredAttachmentStrategy(this);
202 } else { 203 } else {
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
257 dispatchArgs(args); 258 dispatchArgs(args);
258 }; 259 };
259 260
260 // Test if a named event has any listeners. 261 // Test if a named event has any listeners.
261 chromeHidden.Event.hasListener = function(name) { 262 chromeHidden.Event.hasListener = function(name) {
262 return (attachedNamedEvents[name] && 263 return (attachedNamedEvents[name] &&
263 attachedNamedEvents[name].listeners_.length > 0); 264 attachedNamedEvents[name].listeners_.length > 0);
264 }; 265 };
265 266
266 // Registers a callback to be called when this event is dispatched. 267 // Registers a callback to be called when this event is dispatched.
267 chrome.Event.prototype.addListener = function(cb, filters) { 268 Event.prototype.addListener = function(cb, filters) {
268 if (!this.eventOptions_.supportsListeners) 269 if (!this.eventOptions_.supportsListeners)
269 throw new Error("This event does not support listeners."); 270 throw new Error("This event does not support listeners.");
270 if (this.eventOptions_.maxListeners && 271 if (this.eventOptions_.maxListeners &&
271 this.getListenerCount() >= this.eventOptions_.maxListeners) 272 this.getListenerCount() >= this.eventOptions_.maxListeners)
272 throw new Error("Too many listeners for " + this.eventName_); 273 throw new Error("Too many listeners for " + this.eventName_);
273 if (filters) { 274 if (filters) {
274 if (!this.eventOptions_.supportsFilters) 275 if (!this.eventOptions_.supportsFilters)
275 throw new Error("This event does not support filters."); 276 throw new Error("This event does not support filters.");
276 if (filters.url && !(filters.url instanceof Array)) 277 if (filters.url && !(filters.url instanceof Array))
277 throw new Error("filters.url should be an array"); 278 throw new Error("filters.url should be an array");
278 } 279 }
279 var listener = {callback: cb, filters: filters}; 280 var listener = {callback: cb, filters: filters};
280 this.attach_(listener); 281 this.attach_(listener);
281 this.listeners_.push(listener); 282 this.listeners_.push(listener);
282 }; 283 };
283 284
284 chrome.Event.prototype.attach_ = function(listener) { 285 Event.prototype.attach_ = function(listener) {
285 this.attachmentStrategy_.onAddedListener(listener); 286 this.attachmentStrategy_.onAddedListener(listener);
286 if (this.listeners_.length == 0) { 287 if (this.listeners_.length == 0) {
287 allAttachedEvents[allAttachedEvents.length] = this; 288 allAttachedEvents[allAttachedEvents.length] = this;
288 if (!this.eventName_) 289 if (!this.eventName_)
289 return; 290 return;
290 291
291 if (attachedNamedEvents[this.eventName_]) { 292 if (attachedNamedEvents[this.eventName_]) {
292 throw new Error("chrome.Event '" + this.eventName_ + 293 throw new Error("chrome.Event '" + this.eventName_ +
293 "' is already attached."); 294 "' is already attached.");
294 } 295 }
295 296
296 attachedNamedEvents[this.eventName_] = this; 297 attachedNamedEvents[this.eventName_] = this;
297 } 298 }
298 }; 299 };
299 300
300 // Unregisters a callback. 301 // Unregisters a callback.
301 chrome.Event.prototype.removeListener = function(cb) { 302 Event.prototype.removeListener = function(cb) {
302 if (!this.eventOptions_.supportsListeners) 303 if (!this.eventOptions_.supportsListeners)
303 throw new Error("This event does not support listeners."); 304 throw new Error("This event does not support listeners.");
304 var idx = this.findListener_(cb); 305 var idx = this.findListener_(cb);
305 if (idx == -1) { 306 if (idx == -1) {
306 return; 307 return;
307 } 308 }
308 309
309 var removedListener = this.listeners_.splice(idx, 1)[0]; 310 var removedListener = this.listeners_.splice(idx, 1)[0];
310 this.attachmentStrategy_.onRemovedListener(removedListener); 311 this.attachmentStrategy_.onRemovedListener(removedListener);
311 312
312 if (this.listeners_.length == 0) { 313 if (this.listeners_.length == 0) {
313 var i = allAttachedEvents.indexOf(this); 314 var i = allAttachedEvents.indexOf(this);
314 if (i >= 0) 315 if (i >= 0)
315 delete allAttachedEvents[i]; 316 delete allAttachedEvents[i];
316 if (!this.eventName_) 317 if (!this.eventName_)
317 return; 318 return;
318 319
319 if (!attachedNamedEvents[this.eventName_]) { 320 if (!attachedNamedEvents[this.eventName_]) {
320 throw new Error("chrome.Event '" + this.eventName_ + 321 throw new Error("chrome.Event '" + this.eventName_ +
321 "' is not attached."); 322 "' is not attached.");
322 } 323 }
323 324
324 delete attachedNamedEvents[this.eventName_]; 325 delete attachedNamedEvents[this.eventName_];
325 } 326 }
326 }; 327 };
327 328
328 // Test if the given callback is registered for this event. 329 // Test if the given callback is registered for this event.
329 chrome.Event.prototype.hasListener = function(cb) { 330 Event.prototype.hasListener = function(cb) {
330 if (!this.eventOptions_.supportsListeners) 331 if (!this.eventOptions_.supportsListeners)
331 throw new Error("This event does not support listeners."); 332 throw new Error("This event does not support listeners.");
332 return this.findListener_(cb) > -1; 333 return this.findListener_(cb) > -1;
333 }; 334 };
334 335
335 // Test if any callbacks are registered for this event. 336 // Test if any callbacks are registered for this event.
336 chrome.Event.prototype.hasListeners = function() { 337 Event.prototype.hasListeners = function() {
337 return this.getListenerCount() > 0; 338 return this.getListenerCount() > 0;
338 }; 339 };
339 340
340 // Return the number of listeners on this event. 341 // Return the number of listeners on this event.
341 chrome.Event.prototype.getListenerCount = function() { 342 Event.prototype.getListenerCount = function() {
342 if (!this.eventOptions_.supportsListeners) 343 if (!this.eventOptions_.supportsListeners)
343 throw new Error("This event does not support listeners."); 344 throw new Error("This event does not support listeners.");
344 return this.listeners_.length; 345 return this.listeners_.length;
345 }; 346 };
346 347
347 // Returns the index of the given callback if registered, or -1 if not 348 // Returns the index of the given callback if registered, or -1 if not
348 // found. 349 // found.
349 chrome.Event.prototype.findListener_ = function(cb) { 350 Event.prototype.findListener_ = function(cb) {
350 for (var i = 0; i < this.listeners_.length; i++) { 351 for (var i = 0; i < this.listeners_.length; i++) {
351 if (this.listeners_[i].callback == cb) { 352 if (this.listeners_[i].callback == cb) {
352 return i; 353 return i;
353 } 354 }
354 } 355 }
355 356
356 return -1; 357 return -1;
357 }; 358 };
358 359
359 chrome.Event.prototype.dispatch_ = function(args, listenerIDs) { 360 Event.prototype.dispatch_ = function(args, listenerIDs) {
360 if (!this.eventOptions_.supportsListeners) 361 if (!this.eventOptions_.supportsListeners)
361 throw new Error("This event does not support listeners."); 362 throw new Error("This event does not support listeners.");
362 var validationErrors = this.validateEventArgs_(args); 363 var validationErrors = this.validateEventArgs_(args);
363 if (validationErrors) { 364 if (validationErrors) {
364 console.error(validationErrors); 365 console.error(validationErrors);
365 return {validationErrors: validationErrors}; 366 return {validationErrors: validationErrors};
366 } 367 }
367 368
368 // Make a copy of the listeners in case the listener list is modified 369 // Make a copy of the listeners in case the listener list is modified
369 // while dispatching the event. 370 // while dispatching the event.
370 var listeners = 371 var listeners =
371 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice(); 372 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice();
372 373
373 var results = []; 374 var results = [];
374 for (var i = 0; i < listeners.length; i++) { 375 for (var i = 0; i < listeners.length; i++) {
375 try { 376 try {
376 var result = this.dispatchToListener(listeners[i].callback, args); 377 var result = this.dispatchToListener(listeners[i].callback, args);
377 if (result !== undefined) 378 if (result !== undefined)
378 results.push(result); 379 results.push(result);
379 } catch (e) { 380 } catch (e) {
380 console.error("Error in event handler for '" + this.eventName_ + 381 console.error("Error in event handler for '" + this.eventName_ +
381 "': " + e.message + ' ' + e.stack); 382 "': " + e.message + ' ' + e.stack);
382 } 383 }
383 } 384 }
384 if (results.length) 385 if (results.length)
385 return {results: results}; 386 return {results: results};
386 } 387 }
387 388
388 // Can be overridden to support custom dispatching. 389 // Can be overridden to support custom dispatching.
389 chrome.Event.prototype.dispatchToListener = function(callback, args) { 390 Event.prototype.dispatchToListener = function(callback, args) {
390 return callback.apply(null, args); 391 return callback.apply(null, args);
391 } 392 }
392 393
393 // Dispatches this event object to all listeners, passing all supplied 394 // Dispatches this event object to all listeners, passing all supplied
394 // arguments to this function each listener. 395 // arguments to this function each listener.
395 chrome.Event.prototype.dispatch = function(varargs) { 396 Event.prototype.dispatch = function(varargs) {
396 return this.dispatch_(Array.prototype.slice.call(arguments), undefined); 397 return this.dispatch_(Array.prototype.slice.call(arguments), undefined);
397 }; 398 };
398 399
399 // Detaches this event object from its name. 400 // Detaches this event object from its name.
400 chrome.Event.prototype.detach_ = function() { 401 Event.prototype.detach_ = function() {
401 this.attachmentStrategy_.detach(false); 402 this.attachmentStrategy_.detach(false);
402 }; 403 };
403 404
404 chrome.Event.prototype.destroy_ = function() { 405 Event.prototype.destroy_ = function() {
405 this.listeners_ = []; 406 this.listeners_ = [];
406 this.validateEventArgs_ = []; 407 this.validateEventArgs_ = [];
407 this.detach_(false); 408 this.detach_(false);
408 }; 409 };
409 410
410 chrome.Event.prototype.addRules = function(rules, opt_cb) { 411 Event.prototype.addRules = function(rules, opt_cb) {
411 if (!this.eventOptions_.supportsRules) 412 if (!this.eventOptions_.supportsRules)
412 throw new Error("This event does not support rules."); 413 throw new Error("This event does not support rules.");
413 414
414 // Takes a list of JSON datatype identifiers and returns a schema fragment 415 // Takes a list of JSON datatype identifiers and returns a schema fragment
415 // that verifies that a JSON object corresponds to an array of only these 416 // that verifies that a JSON object corresponds to an array of only these
416 // data types. 417 // data types.
417 function buildArrayOfChoicesSchema(typesList) { 418 function buildArrayOfChoicesSchema(typesList) {
418 return { 419 return {
419 'type': 'array', 420 'type': 'array',
420 'items': { 421 'items': {
(...skipping 29 matching lines...) Expand all
450 451
451 ensureRuleSchemasLoaded(); 452 ensureRuleSchemasLoaded();
452 // We remove the first parameter from the validation to give the user more 453 // We remove the first parameter from the validation to give the user more
453 // meaningful error messages. 454 // meaningful error messages.
454 validate([rules, opt_cb], 455 validate([rules, opt_cb],
455 ruleFunctionSchemas.addRules.parameters.slice().splice(1)); 456 ruleFunctionSchemas.addRules.parameters.slice().splice(1));
456 sendRequest("events.addRules", [this.eventName_, rules, opt_cb], 457 sendRequest("events.addRules", [this.eventName_, rules, opt_cb],
457 ruleFunctionSchemas.addRules.parameters); 458 ruleFunctionSchemas.addRules.parameters);
458 } 459 }
459 460
460 chrome.Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { 461 Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) {
461 if (!this.eventOptions_.supportsRules) 462 if (!this.eventOptions_.supportsRules)
462 throw new Error("This event does not support rules."); 463 throw new Error("This event does not support rules.");
463 ensureRuleSchemasLoaded(); 464 ensureRuleSchemasLoaded();
464 // We remove the first parameter from the validation to give the user more 465 // We remove the first parameter from the validation to give the user more
465 // meaningful error messages. 466 // meaningful error messages.
466 validate([ruleIdentifiers, opt_cb], 467 validate([ruleIdentifiers, opt_cb],
467 ruleFunctionSchemas.removeRules.parameters.slice().splice(1)); 468 ruleFunctionSchemas.removeRules.parameters.slice().splice(1));
468 sendRequest("events.removeRules", 469 sendRequest("events.removeRules",
469 [this.eventName_, ruleIdentifiers, opt_cb], 470 [this.eventName_, ruleIdentifiers, opt_cb],
470 ruleFunctionSchemas.removeRules.parameters); 471 ruleFunctionSchemas.removeRules.parameters);
471 } 472 }
472 473
473 chrome.Event.prototype.getRules = function(ruleIdentifiers, cb) { 474 Event.prototype.getRules = function(ruleIdentifiers, cb) {
474 if (!this.eventOptions_.supportsRules) 475 if (!this.eventOptions_.supportsRules)
475 throw new Error("This event does not support rules."); 476 throw new Error("This event does not support rules.");
476 ensureRuleSchemasLoaded(); 477 ensureRuleSchemasLoaded();
477 // We remove the first parameter from the validation to give the user more 478 // We remove the first parameter from the validation to give the user more
478 // meaningful error messages. 479 // meaningful error messages.
479 validate([ruleIdentifiers, cb], 480 validate([ruleIdentifiers, cb],
480 ruleFunctionSchemas.getRules.parameters.slice().splice(1)); 481 ruleFunctionSchemas.getRules.parameters.slice().splice(1));
481 482
482 sendRequest("events.getRules", 483 sendRequest("events.getRules",
483 [this.eventName_, ruleIdentifiers, cb], 484 [this.eventName_, ruleIdentifiers, cb],
484 ruleFunctionSchemas.getRules.parameters); 485 ruleFunctionSchemas.getRules.parameters);
485 } 486 }
486 487
487 // Special load events: we don't use the DOM unload because that slows 488 // Special load events: we don't use the DOM unload because that slows
488 // down tab shutdown. On the other hand, onUnload might not always fire, 489 // down tab shutdown. On the other hand, onUnload might not always fire,
489 // since Chrome will terminate renderers on shutdown (SuddenTermination). 490 // since Chrome will terminate renderers on shutdown (SuddenTermination).
490 chromeHidden.onLoad = new chrome.Event(); 491 chromeHidden.onLoad = new Event();
491 chromeHidden.onUnload = new chrome.Event(); 492 chromeHidden.onUnload = new Event();
492 493
493 chromeHidden.dispatchOnLoad = 494 chromeHidden.dispatchOnLoad =
494 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); 495 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad);
495 496
496 chromeHidden.dispatchOnUnload = function() { 497 chromeHidden.dispatchOnUnload = function() {
497 chromeHidden.onUnload.dispatch(); 498 chromeHidden.onUnload.dispatch();
498 for (var i = 0; i < allAttachedEvents.length; ++i) { 499 for (var i = 0; i < allAttachedEvents.length; ++i) {
499 var event = allAttachedEvents[i]; 500 var event = allAttachedEvents[i];
500 if (event) 501 if (event)
501 event.detach_(); 502 event.detach_();
502 } 503 }
503 }; 504 };
504 505
505 chromeHidden.dispatchError = function(msg) { 506 chromeHidden.dispatchError = function(msg) {
506 console.error(msg); 507 console.error(msg);
507 }; 508 };
508 509
509 exports.Event = chrome.Event; 510 chrome.Event = Event;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698