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

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: android compilation Created 7 years, 9 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 // TODO(cduvall/kalman): json_schema shouldn't put things on chromeHidden.
7 require('json_schema');
6 var eventBindingsNatives = requireNative('event_bindings'); 8 var eventBindingsNatives = requireNative('event_bindings');
7 var AttachEvent = eventBindingsNatives.AttachEvent; 9 var AttachEvent = eventBindingsNatives.AttachEvent;
8 var DetachEvent = eventBindingsNatives.DetachEvent; 10 var DetachEvent = eventBindingsNatives.DetachEvent;
9 var AttachFilteredEvent = eventBindingsNatives.AttachFilteredEvent; 11 var AttachFilteredEvent = eventBindingsNatives.AttachFilteredEvent;
10 var DetachFilteredEvent = eventBindingsNatives.DetachFilteredEvent; 12 var DetachFilteredEvent = eventBindingsNatives.DetachFilteredEvent;
11 var MatchAgainstEventFilter = eventBindingsNatives.MatchAgainstEventFilter; 13 var MatchAgainstEventFilter = eventBindingsNatives.MatchAgainstEventFilter;
12 var sendRequest = require('sendRequest').sendRequest; 14 var sendRequest = require('sendRequest').sendRequest;
13 var utils = require('utils'); 15 var utils = require('utils');
14 var validate = require('schemaUtils').validate; 16 var validate = require('schemaUtils').validate;
15 17
16 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); 18 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden();
17 var GetExtensionAPIDefinition = 19 var chrome = requireNative('chrome').GetChrome();
18 requireNative('apiDefinitions').GetExtensionAPIDefinition; 20 var schemaRegistry = requireNative('schema_registry');
19 21
20 // Schemas for the rule-style functions on the events API that 22 // Schemas for the rule-style functions on the events API that
21 // only need to be generated occasionally, so populate them lazily. 23 // only need to be generated occasionally, so populate them lazily.
22 var ruleFunctionSchemas = { 24 var ruleFunctionSchemas = {
23 // These values are set lazily: 25 // These values are set lazily:
24 // addRules: {}, 26 // addRules: {},
25 // getRules: {}, 27 // getRules: {},
26 // removeRules: {} 28 // removeRules: {}
27 }; 29 };
28 30
29 // This function ensures that |ruleFunctionSchemas| is populated. 31 // This function ensures that |ruleFunctionSchemas| is populated.
30 function ensureRuleSchemasLoaded() { 32 function ensureRuleSchemasLoaded() {
31 if (ruleFunctionSchemas.addRules) 33 if (ruleFunctionSchemas.addRules)
32 return; 34 return;
33 var eventsSchema = GetExtensionAPIDefinition("events")[0]; 35 var eventsSchema = schemaRegistry.GetSchema("events");
34 var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event'); 36 var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event');
35 37
36 ruleFunctionSchemas.addRules = 38 ruleFunctionSchemas.addRules =
37 utils.lookup(eventType.functions, 'name', 'addRules'); 39 utils.lookup(eventType.functions, 'name', 'addRules');
38 ruleFunctionSchemas.getRules = 40 ruleFunctionSchemas.getRules =
39 utils.lookup(eventType.functions, 'name', 'getRules'); 41 utils.lookup(eventType.functions, 'name', 'getRules');
40 ruleFunctionSchemas.removeRules = 42 ruleFunctionSchemas.removeRules =
41 utils.lookup(eventType.functions, 'name', 'removeRules'); 43 utils.lookup(eventType.functions, 'name', 'removeRules');
42 } 44 }
43 45
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after
144 // opt_eventName is required for events that support rules. 146 // opt_eventName is required for events that support rules.
145 // 147 //
146 // Example: 148 // Example:
147 // chrome.tabs.onChanged = new chrome.Event("tab-changed"); 149 // chrome.tabs.onChanged = new chrome.Event("tab-changed");
148 // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); 150 // chrome.tabs.onChanged.addListener(function(data) { alert(data); });
149 // chromeHidden.Event.dispatch("tab-changed", "hi"); 151 // chromeHidden.Event.dispatch("tab-changed", "hi");
150 // will result in an alert dialog that says 'hi'. 152 // will result in an alert dialog that says 'hi'.
151 // 153 //
152 // If opt_eventOptions exists, it is a dictionary that contains the boolean 154 // If opt_eventOptions exists, it is a dictionary that contains the boolean
153 // entries "supportsListeners" and "supportsRules". 155 // entries "supportsListeners" and "supportsRules".
154 chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) { 156 var Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) {
155 this.eventName_ = opt_eventName; 157 this.eventName_ = opt_eventName;
156 this.listeners_ = []; 158 this.listeners_ = [];
157 this.eventOptions_ = chromeHidden.parseEventOptions(opt_eventOptions); 159 this.eventOptions_ = chromeHidden.parseEventOptions(opt_eventOptions);
158 160
159 if (this.eventOptions_.supportsRules && !opt_eventName) 161 if (this.eventOptions_.supportsRules && !opt_eventName)
160 throw new Error("Events that support rules require an event name."); 162 throw new Error("Events that support rules require an event name.");
161 163
162 if (this.eventOptions_.supportsFilters) { 164 if (this.eventOptions_.supportsFilters) {
163 this.attachmentStrategy_ = new FilteredAttachmentStrategy(this); 165 this.attachmentStrategy_ = new FilteredAttachmentStrategy(this);
164 } else { 166 } else {
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
220 dispatchArgs(args); 222 dispatchArgs(args);
221 }; 223 };
222 224
223 // Test if a named event has any listeners. 225 // Test if a named event has any listeners.
224 chromeHidden.Event.hasListener = function(name) { 226 chromeHidden.Event.hasListener = function(name) {
225 return (attachedNamedEvents[name] && 227 return (attachedNamedEvents[name] &&
226 attachedNamedEvents[name].listeners_.length > 0); 228 attachedNamedEvents[name].listeners_.length > 0);
227 }; 229 };
228 230
229 // Registers a callback to be called when this event is dispatched. 231 // Registers a callback to be called when this event is dispatched.
230 chrome.Event.prototype.addListener = function(cb, filters) { 232 Event.prototype.addListener = function(cb, filters) {
231 if (!this.eventOptions_.supportsListeners) 233 if (!this.eventOptions_.supportsListeners)
232 throw new Error("This event does not support listeners."); 234 throw new Error("This event does not support listeners.");
233 if (this.eventOptions_.maxListeners && 235 if (this.eventOptions_.maxListeners &&
234 this.getListenerCount() >= this.eventOptions_.maxListeners) 236 this.getListenerCount() >= this.eventOptions_.maxListeners)
235 throw new Error("Too many listeners for " + this.eventName_); 237 throw new Error("Too many listeners for " + this.eventName_);
236 if (filters) { 238 if (filters) {
237 if (!this.eventOptions_.supportsFilters) 239 if (!this.eventOptions_.supportsFilters)
238 throw new Error("This event does not support filters."); 240 throw new Error("This event does not support filters.");
239 if (filters.url && !(filters.url instanceof Array)) 241 if (filters.url && !(filters.url instanceof Array))
240 throw new Error("filters.url should be an array"); 242 throw new Error("filters.url should be an array");
241 } 243 }
242 var listener = {callback: cb, filters: filters}; 244 var listener = {callback: cb, filters: filters};
243 this.attach_(listener); 245 this.attach_(listener);
244 this.listeners_.push(listener); 246 this.listeners_.push(listener);
245 }; 247 };
246 248
247 chrome.Event.prototype.attach_ = function(listener) { 249 Event.prototype.attach_ = function(listener) {
248 this.attachmentStrategy_.onAddedListener(listener); 250 this.attachmentStrategy_.onAddedListener(listener);
249 if (this.listeners_.length == 0) { 251 if (this.listeners_.length == 0) {
250 allAttachedEvents[allAttachedEvents.length] = this; 252 allAttachedEvents[allAttachedEvents.length] = this;
251 if (!this.eventName_) 253 if (!this.eventName_)
252 return; 254 return;
253 255
254 if (attachedNamedEvents[this.eventName_]) { 256 if (attachedNamedEvents[this.eventName_]) {
255 throw new Error("chrome.Event '" + this.eventName_ + 257 throw new Error("chrome.Event '" + this.eventName_ +
256 "' is already attached."); 258 "' is already attached.");
257 } 259 }
258 260
259 attachedNamedEvents[this.eventName_] = this; 261 attachedNamedEvents[this.eventName_] = this;
260 } 262 }
261 }; 263 };
262 264
263 // Unregisters a callback. 265 // Unregisters a callback.
264 chrome.Event.prototype.removeListener = function(cb) { 266 Event.prototype.removeListener = function(cb) {
265 if (!this.eventOptions_.supportsListeners) 267 if (!this.eventOptions_.supportsListeners)
266 throw new Error("This event does not support listeners."); 268 throw new Error("This event does not support listeners.");
267 var idx = this.findListener_(cb); 269 var idx = this.findListener_(cb);
268 if (idx == -1) { 270 if (idx == -1) {
269 return; 271 return;
270 } 272 }
271 273
272 var removedListener = this.listeners_.splice(idx, 1)[0]; 274 var removedListener = this.listeners_.splice(idx, 1)[0];
273 this.attachmentStrategy_.onRemovedListener(removedListener); 275 this.attachmentStrategy_.onRemovedListener(removedListener);
274 276
275 if (this.listeners_.length == 0) { 277 if (this.listeners_.length == 0) {
276 var i = allAttachedEvents.indexOf(this); 278 var i = allAttachedEvents.indexOf(this);
277 if (i >= 0) 279 if (i >= 0)
278 delete allAttachedEvents[i]; 280 delete allAttachedEvents[i];
279 if (!this.eventName_) 281 if (!this.eventName_)
280 return; 282 return;
281 283
282 if (!attachedNamedEvents[this.eventName_]) { 284 if (!attachedNamedEvents[this.eventName_]) {
283 throw new Error("chrome.Event '" + this.eventName_ + 285 throw new Error("chrome.Event '" + this.eventName_ +
284 "' is not attached."); 286 "' is not attached.");
285 } 287 }
286 288
287 delete attachedNamedEvents[this.eventName_]; 289 delete attachedNamedEvents[this.eventName_];
288 } 290 }
289 }; 291 };
290 292
291 // Test if the given callback is registered for this event. 293 // Test if the given callback is registered for this event.
292 chrome.Event.prototype.hasListener = function(cb) { 294 Event.prototype.hasListener = function(cb) {
293 if (!this.eventOptions_.supportsListeners) 295 if (!this.eventOptions_.supportsListeners)
294 throw new Error("This event does not support listeners."); 296 throw new Error("This event does not support listeners.");
295 return this.findListener_(cb) > -1; 297 return this.findListener_(cb) > -1;
296 }; 298 };
297 299
298 // Test if any callbacks are registered for this event. 300 // Test if any callbacks are registered for this event.
299 chrome.Event.prototype.hasListeners = function() { 301 Event.prototype.hasListeners = function() {
300 return this.getListenerCount() > 0; 302 return this.getListenerCount() > 0;
301 }; 303 };
302 304
303 // Return the number of listeners on this event. 305 // Return the number of listeners on this event.
304 chrome.Event.prototype.getListenerCount = function() { 306 Event.prototype.getListenerCount = function() {
305 if (!this.eventOptions_.supportsListeners) 307 if (!this.eventOptions_.supportsListeners)
306 throw new Error("This event does not support listeners."); 308 throw new Error("This event does not support listeners.");
307 return this.listeners_.length; 309 return this.listeners_.length;
308 }; 310 };
309 311
310 // Returns the index of the given callback if registered, or -1 if not 312 // Returns the index of the given callback if registered, or -1 if not
311 // found. 313 // found.
312 chrome.Event.prototype.findListener_ = function(cb) { 314 Event.prototype.findListener_ = function(cb) {
313 for (var i = 0; i < this.listeners_.length; i++) { 315 for (var i = 0; i < this.listeners_.length; i++) {
314 if (this.listeners_[i].callback == cb) { 316 if (this.listeners_[i].callback == cb) {
315 return i; 317 return i;
316 } 318 }
317 } 319 }
318 320
319 return -1; 321 return -1;
320 }; 322 };
321 323
322 chrome.Event.prototype.dispatch_ = function(args, listenerIDs) { 324 Event.prototype.dispatch_ = function(args, listenerIDs) {
323 if (!this.eventOptions_.supportsListeners) 325 if (!this.eventOptions_.supportsListeners)
324 throw new Error("This event does not support listeners."); 326 throw new Error("This event does not support listeners.");
325 var validationErrors = this.validateEventArgs_(args); 327 var validationErrors = this.validateEventArgs_(args);
326 if (validationErrors) { 328 if (validationErrors) {
327 console.error(validationErrors); 329 console.error(validationErrors);
328 return {validationErrors: validationErrors}; 330 return {validationErrors: validationErrors};
329 } 331 }
330 332
331 // Make a copy of the listeners in case the listener list is modified 333 // Make a copy of the listeners in case the listener list is modified
332 // while dispatching the event. 334 // while dispatching the event.
333 var listeners = 335 var listeners =
334 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice(); 336 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice();
335 337
336 var results = []; 338 var results = [];
337 for (var i = 0; i < listeners.length; i++) { 339 for (var i = 0; i < listeners.length; i++) {
338 try { 340 try {
339 var result = this.dispatchToListener(listeners[i].callback, args); 341 var result = this.dispatchToListener(listeners[i].callback, args);
340 if (result !== undefined) 342 if (result !== undefined)
341 results.push(result); 343 results.push(result);
342 } catch (e) { 344 } catch (e) {
343 console.error("Error in event handler for '" + this.eventName_ + 345 console.error("Error in event handler for '" + this.eventName_ +
344 "': " + e.message + ' ' + e.stack); 346 "': " + e.message + ' ' + e.stack);
345 } 347 }
346 } 348 }
347 if (results.length) 349 if (results.length)
348 return {results: results}; 350 return {results: results};
349 } 351 }
350 352
351 // Can be overridden to support custom dispatching. 353 // Can be overridden to support custom dispatching.
352 chrome.Event.prototype.dispatchToListener = function(callback, args) { 354 Event.prototype.dispatchToListener = function(callback, args) {
353 return callback.apply(null, args); 355 return callback.apply(null, args);
354 } 356 }
355 357
356 // Dispatches this event object to all listeners, passing all supplied 358 // Dispatches this event object to all listeners, passing all supplied
357 // arguments to this function each listener. 359 // arguments to this function each listener.
358 chrome.Event.prototype.dispatch = function(varargs) { 360 Event.prototype.dispatch = function(varargs) {
359 return this.dispatch_(Array.prototype.slice.call(arguments), undefined); 361 return this.dispatch_(Array.prototype.slice.call(arguments), undefined);
360 }; 362 };
361 363
362 // Detaches this event object from its name. 364 // Detaches this event object from its name.
363 chrome.Event.prototype.detach_ = function() { 365 Event.prototype.detach_ = function() {
364 this.attachmentStrategy_.detach(false); 366 this.attachmentStrategy_.detach(false);
365 }; 367 };
366 368
367 chrome.Event.prototype.destroy_ = function() { 369 Event.prototype.destroy_ = function() {
368 this.listeners_ = []; 370 this.listeners_ = [];
369 this.validateEventArgs_ = []; 371 this.validateEventArgs_ = [];
370 this.detach_(false); 372 this.detach_(false);
371 }; 373 };
372 374
373 chrome.Event.prototype.addRules = function(rules, opt_cb) { 375 Event.prototype.addRules = function(rules, opt_cb) {
374 if (!this.eventOptions_.supportsRules) 376 if (!this.eventOptions_.supportsRules)
375 throw new Error("This event does not support rules."); 377 throw new Error("This event does not support rules.");
376 378
377 // Takes a list of JSON datatype identifiers and returns a schema fragment 379 // Takes a list of JSON datatype identifiers and returns a schema fragment
378 // that verifies that a JSON object corresponds to an array of only these 380 // that verifies that a JSON object corresponds to an array of only these
379 // data types. 381 // data types.
380 function buildArrayOfChoicesSchema(typesList) { 382 function buildArrayOfChoicesSchema(typesList) {
381 return { 383 return {
382 'type': 'array', 384 'type': 'array',
383 'items': { 385 'items': {
(...skipping 29 matching lines...) Expand all
413 415
414 ensureRuleSchemasLoaded(); 416 ensureRuleSchemasLoaded();
415 // We remove the first parameter from the validation to give the user more 417 // We remove the first parameter from the validation to give the user more
416 // meaningful error messages. 418 // meaningful error messages.
417 validate([rules, opt_cb], 419 validate([rules, opt_cb],
418 ruleFunctionSchemas.addRules.parameters.slice().splice(1)); 420 ruleFunctionSchemas.addRules.parameters.slice().splice(1));
419 sendRequest("events.addRules", [this.eventName_, rules, opt_cb], 421 sendRequest("events.addRules", [this.eventName_, rules, opt_cb],
420 ruleFunctionSchemas.addRules.parameters); 422 ruleFunctionSchemas.addRules.parameters);
421 } 423 }
422 424
423 chrome.Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { 425 Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) {
424 if (!this.eventOptions_.supportsRules) 426 if (!this.eventOptions_.supportsRules)
425 throw new Error("This event does not support rules."); 427 throw new Error("This event does not support rules.");
426 ensureRuleSchemasLoaded(); 428 ensureRuleSchemasLoaded();
427 // We remove the first parameter from the validation to give the user more 429 // We remove the first parameter from the validation to give the user more
428 // meaningful error messages. 430 // meaningful error messages.
429 validate([ruleIdentifiers, opt_cb], 431 validate([ruleIdentifiers, opt_cb],
430 ruleFunctionSchemas.removeRules.parameters.slice().splice(1)); 432 ruleFunctionSchemas.removeRules.parameters.slice().splice(1));
431 sendRequest("events.removeRules", 433 sendRequest("events.removeRules",
432 [this.eventName_, ruleIdentifiers, opt_cb], 434 [this.eventName_, ruleIdentifiers, opt_cb],
433 ruleFunctionSchemas.removeRules.parameters); 435 ruleFunctionSchemas.removeRules.parameters);
434 } 436 }
435 437
436 chrome.Event.prototype.getRules = function(ruleIdentifiers, cb) { 438 Event.prototype.getRules = function(ruleIdentifiers, cb) {
437 if (!this.eventOptions_.supportsRules) 439 if (!this.eventOptions_.supportsRules)
438 throw new Error("This event does not support rules."); 440 throw new Error("This event does not support rules.");
439 ensureRuleSchemasLoaded(); 441 ensureRuleSchemasLoaded();
440 // We remove the first parameter from the validation to give the user more 442 // We remove the first parameter from the validation to give the user more
441 // meaningful error messages. 443 // meaningful error messages.
442 validate([ruleIdentifiers, cb], 444 validate([ruleIdentifiers, cb],
443 ruleFunctionSchemas.getRules.parameters.slice().splice(1)); 445 ruleFunctionSchemas.getRules.parameters.slice().splice(1));
444 446
445 sendRequest("events.getRules", 447 sendRequest("events.getRules",
446 [this.eventName_, ruleIdentifiers, cb], 448 [this.eventName_, ruleIdentifiers, cb],
447 ruleFunctionSchemas.getRules.parameters); 449 ruleFunctionSchemas.getRules.parameters);
448 } 450 }
449 451
450 // Special load events: we don't use the DOM unload because that slows 452 // Special load events: we don't use the DOM unload because that slows
451 // down tab shutdown. On the other hand, onUnload might not always fire, 453 // down tab shutdown. On the other hand, onUnload might not always fire,
452 // since Chrome will terminate renderers on shutdown (SuddenTermination). 454 // since Chrome will terminate renderers on shutdown (SuddenTermination).
453 chromeHidden.onLoad = new chrome.Event(); 455 chromeHidden.onLoad = new Event();
454 chromeHidden.onUnload = new chrome.Event(); 456 chromeHidden.onUnload = new Event();
455 457
456 chromeHidden.dispatchOnLoad = 458 chromeHidden.dispatchOnLoad =
457 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); 459 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad);
458 460
459 chromeHidden.dispatchOnUnload = function() { 461 chromeHidden.dispatchOnUnload = function() {
460 chromeHidden.onUnload.dispatch(); 462 chromeHidden.onUnload.dispatch();
461 chromeHidden.wasUnloaded = true; 463 chromeHidden.wasUnloaded = true;
462 464
463 for (var i = 0; i < allAttachedEvents.length; ++i) { 465 for (var i = 0; i < allAttachedEvents.length; ++i) {
464 var event = allAttachedEvents[i]; 466 var event = allAttachedEvents[i];
465 if (event) 467 if (event)
466 event.detach_(); 468 event.detach_();
467 } 469 }
468 }; 470 };
469 471
470 chromeHidden.dispatchError = function(msg) { 472 chromeHidden.dispatchError = function(msg) {
471 console.error(msg); 473 console.error(msg);
472 }; 474 };
473 475
474 exports.Event = chrome.Event; 476 chrome.Event = Event;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698