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

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: fix interactive_ui_tests 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 // 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 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
219 dispatchArgs(args); 221 dispatchArgs(args);
220 }; 222 };
221 223
222 // Test if a named event has any listeners. 224 // Test if a named event has any listeners.
223 chromeHidden.Event.hasListener = function(name) { 225 chromeHidden.Event.hasListener = function(name) {
224 return (attachedNamedEvents[name] && 226 return (attachedNamedEvents[name] &&
225 attachedNamedEvents[name].listeners_.length > 0); 227 attachedNamedEvents[name].listeners_.length > 0);
226 }; 228 };
227 229
228 // Registers a callback to be called when this event is dispatched. 230 // Registers a callback to be called when this event is dispatched.
229 chrome.Event.prototype.addListener = function(cb, filters) { 231 Event.prototype.addListener = function(cb, filters) {
230 if (!this.eventOptions_.supportsListeners) 232 if (!this.eventOptions_.supportsListeners)
231 throw new Error("This event does not support listeners."); 233 throw new Error("This event does not support listeners.");
232 if (this.eventOptions_.maxListeners && 234 if (this.eventOptions_.maxListeners &&
233 this.getListenerCount() >= this.eventOptions_.maxListeners) 235 this.getListenerCount() >= this.eventOptions_.maxListeners)
234 throw new Error("Too many listeners for " + this.eventName_); 236 throw new Error("Too many listeners for " + this.eventName_);
235 if (filters) { 237 if (filters) {
236 if (!this.eventOptions_.supportsFilters) 238 if (!this.eventOptions_.supportsFilters)
237 throw new Error("This event does not support filters."); 239 throw new Error("This event does not support filters.");
238 if (filters.url && !(filters.url instanceof Array)) 240 if (filters.url && !(filters.url instanceof Array))
239 throw new Error("filters.url should be an array"); 241 throw new Error("filters.url should be an array");
240 } 242 }
241 var listener = {callback: cb, filters: filters}; 243 var listener = {callback: cb, filters: filters};
242 this.attach_(listener); 244 this.attach_(listener);
243 this.listeners_.push(listener); 245 this.listeners_.push(listener);
244 }; 246 };
245 247
246 chrome.Event.prototype.attach_ = function(listener) { 248 Event.prototype.attach_ = function(listener) {
247 this.attachmentStrategy_.onAddedListener(listener); 249 this.attachmentStrategy_.onAddedListener(listener);
248 if (this.listeners_.length == 0) { 250 if (this.listeners_.length == 0) {
249 allAttachedEvents[allAttachedEvents.length] = this; 251 allAttachedEvents[allAttachedEvents.length] = this;
250 if (!this.eventName_) 252 if (!this.eventName_)
251 return; 253 return;
252 254
253 if (attachedNamedEvents[this.eventName_]) { 255 if (attachedNamedEvents[this.eventName_]) {
254 throw new Error("chrome.Event '" + this.eventName_ + 256 throw new Error("chrome.Event '" + this.eventName_ +
255 "' is already attached."); 257 "' is already attached.");
256 } 258 }
257 259
258 attachedNamedEvents[this.eventName_] = this; 260 attachedNamedEvents[this.eventName_] = this;
259 } 261 }
260 }; 262 };
261 263
262 // Unregisters a callback. 264 // Unregisters a callback.
263 chrome.Event.prototype.removeListener = function(cb) { 265 Event.prototype.removeListener = function(cb) {
264 if (!this.eventOptions_.supportsListeners) 266 if (!this.eventOptions_.supportsListeners)
265 throw new Error("This event does not support listeners."); 267 throw new Error("This event does not support listeners.");
266 var idx = this.findListener_(cb); 268 var idx = this.findListener_(cb);
267 if (idx == -1) { 269 if (idx == -1) {
268 return; 270 return;
269 } 271 }
270 272
271 var removedListener = this.listeners_.splice(idx, 1)[0]; 273 var removedListener = this.listeners_.splice(idx, 1)[0];
272 this.attachmentStrategy_.onRemovedListener(removedListener); 274 this.attachmentStrategy_.onRemovedListener(removedListener);
273 275
274 if (this.listeners_.length == 0) { 276 if (this.listeners_.length == 0) {
275 var i = allAttachedEvents.indexOf(this); 277 var i = allAttachedEvents.indexOf(this);
276 if (i >= 0) 278 if (i >= 0)
277 delete allAttachedEvents[i]; 279 delete allAttachedEvents[i];
278 if (!this.eventName_) 280 if (!this.eventName_)
279 return; 281 return;
280 282
281 if (!attachedNamedEvents[this.eventName_]) { 283 if (!attachedNamedEvents[this.eventName_]) {
282 throw new Error("chrome.Event '" + this.eventName_ + 284 throw new Error("chrome.Event '" + this.eventName_ +
283 "' is not attached."); 285 "' is not attached.");
284 } 286 }
285 287
286 delete attachedNamedEvents[this.eventName_]; 288 delete attachedNamedEvents[this.eventName_];
287 } 289 }
288 }; 290 };
289 291
290 // Test if the given callback is registered for this event. 292 // Test if the given callback is registered for this event.
291 chrome.Event.prototype.hasListener = function(cb) { 293 Event.prototype.hasListener = function(cb) {
292 if (!this.eventOptions_.supportsListeners) 294 if (!this.eventOptions_.supportsListeners)
293 throw new Error("This event does not support listeners."); 295 throw new Error("This event does not support listeners.");
294 return this.findListener_(cb) > -1; 296 return this.findListener_(cb) > -1;
295 }; 297 };
296 298
297 // Test if any callbacks are registered for this event. 299 // Test if any callbacks are registered for this event.
298 chrome.Event.prototype.hasListeners = function() { 300 Event.prototype.hasListeners = function() {
299 return this.getListenerCount() > 0; 301 return this.getListenerCount() > 0;
300 }; 302 };
301 303
302 // Return the number of listeners on this event. 304 // Return the number of listeners on this event.
303 chrome.Event.prototype.getListenerCount = function() { 305 Event.prototype.getListenerCount = function() {
304 if (!this.eventOptions_.supportsListeners) 306 if (!this.eventOptions_.supportsListeners)
305 throw new Error("This event does not support listeners."); 307 throw new Error("This event does not support listeners.");
306 return this.listeners_.length; 308 return this.listeners_.length;
307 }; 309 };
308 310
309 // Returns the index of the given callback if registered, or -1 if not 311 // Returns the index of the given callback if registered, or -1 if not
310 // found. 312 // found.
311 chrome.Event.prototype.findListener_ = function(cb) { 313 Event.prototype.findListener_ = function(cb) {
312 for (var i = 0; i < this.listeners_.length; i++) { 314 for (var i = 0; i < this.listeners_.length; i++) {
313 if (this.listeners_[i].callback == cb) { 315 if (this.listeners_[i].callback == cb) {
314 return i; 316 return i;
315 } 317 }
316 } 318 }
317 319
318 return -1; 320 return -1;
319 }; 321 };
320 322
321 chrome.Event.prototype.dispatch_ = function(args, listenerIDs) { 323 Event.prototype.dispatch_ = function(args, listenerIDs) {
322 if (!this.eventOptions_.supportsListeners) 324 if (!this.eventOptions_.supportsListeners)
323 throw new Error("This event does not support listeners."); 325 throw new Error("This event does not support listeners.");
324 var validationErrors = this.validateEventArgs_(args); 326 var validationErrors = this.validateEventArgs_(args);
325 if (validationErrors) { 327 if (validationErrors) {
326 console.error(validationErrors); 328 console.error(validationErrors);
327 return {validationErrors: validationErrors}; 329 return {validationErrors: validationErrors};
328 } 330 }
329 331
330 // Make a copy of the listeners in case the listener list is modified 332 // Make a copy of the listeners in case the listener list is modified
331 // while dispatching the event. 333 // while dispatching the event.
332 var listeners = 334 var listeners =
333 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice(); 335 this.attachmentStrategy_.getListenersByIDs(listenerIDs).slice();
334 336
335 var results = []; 337 var results = [];
336 for (var i = 0; i < listeners.length; i++) { 338 for (var i = 0; i < listeners.length; i++) {
337 try { 339 try {
338 var result = this.dispatchToListener(listeners[i].callback, args); 340 var result = this.dispatchToListener(listeners[i].callback, args);
339 if (result !== undefined) 341 if (result !== undefined)
340 results.push(result); 342 results.push(result);
341 } catch (e) { 343 } catch (e) {
342 console.error("Error in event handler for '" + this.eventName_ + 344 console.error("Error in event handler for '" + this.eventName_ +
343 "': " + e.message + ' ' + e.stack); 345 "': " + e.message + ' ' + e.stack);
344 } 346 }
345 } 347 }
346 if (results.length) 348 if (results.length)
347 return {results: results}; 349 return {results: results};
348 } 350 }
349 351
350 // Can be overridden to support custom dispatching. 352 // Can be overridden to support custom dispatching.
351 chrome.Event.prototype.dispatchToListener = function(callback, args) { 353 Event.prototype.dispatchToListener = function(callback, args) {
352 return callback.apply(null, args); 354 return callback.apply(null, args);
353 } 355 }
354 356
355 // Dispatches this event object to all listeners, passing all supplied 357 // Dispatches this event object to all listeners, passing all supplied
356 // arguments to this function each listener. 358 // arguments to this function each listener.
357 chrome.Event.prototype.dispatch = function(varargs) { 359 Event.prototype.dispatch = function(varargs) {
358 return this.dispatch_(Array.prototype.slice.call(arguments), undefined); 360 return this.dispatch_(Array.prototype.slice.call(arguments), undefined);
359 }; 361 };
360 362
361 // Detaches this event object from its name. 363 // Detaches this event object from its name.
362 chrome.Event.prototype.detach_ = function() { 364 Event.prototype.detach_ = function() {
363 this.attachmentStrategy_.detach(false); 365 this.attachmentStrategy_.detach(false);
364 }; 366 };
365 367
366 chrome.Event.prototype.destroy_ = function() { 368 Event.prototype.destroy_ = function() {
367 this.listeners_ = []; 369 this.listeners_ = [];
368 this.validateEventArgs_ = []; 370 this.validateEventArgs_ = [];
369 this.detach_(false); 371 this.detach_(false);
370 }; 372 };
371 373
372 chrome.Event.prototype.addRules = function(rules, opt_cb) { 374 Event.prototype.addRules = function(rules, opt_cb) {
373 if (!this.eventOptions_.supportsRules) 375 if (!this.eventOptions_.supportsRules)
374 throw new Error("This event does not support rules."); 376 throw new Error("This event does not support rules.");
375 377
376 // Takes a list of JSON datatype identifiers and returns a schema fragment 378 // Takes a list of JSON datatype identifiers and returns a schema fragment
377 // that verifies that a JSON object corresponds to an array of only these 379 // that verifies that a JSON object corresponds to an array of only these
378 // data types. 380 // data types.
379 function buildArrayOfChoicesSchema(typesList) { 381 function buildArrayOfChoicesSchema(typesList) {
380 return { 382 return {
381 'type': 'array', 383 'type': 'array',
382 'items': { 384 'items': {
(...skipping 29 matching lines...) Expand all
412 414
413 ensureRuleSchemasLoaded(); 415 ensureRuleSchemasLoaded();
414 // We remove the first parameter from the validation to give the user more 416 // We remove the first parameter from the validation to give the user more
415 // meaningful error messages. 417 // meaningful error messages.
416 validate([rules, opt_cb], 418 validate([rules, opt_cb],
417 ruleFunctionSchemas.addRules.parameters.slice().splice(1)); 419 ruleFunctionSchemas.addRules.parameters.slice().splice(1));
418 sendRequest("events.addRules", [this.eventName_, rules, opt_cb], 420 sendRequest("events.addRules", [this.eventName_, rules, opt_cb],
419 ruleFunctionSchemas.addRules.parameters); 421 ruleFunctionSchemas.addRules.parameters);
420 } 422 }
421 423
422 chrome.Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) { 424 Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) {
423 if (!this.eventOptions_.supportsRules) 425 if (!this.eventOptions_.supportsRules)
424 throw new Error("This event does not support rules."); 426 throw new Error("This event does not support rules.");
425 ensureRuleSchemasLoaded(); 427 ensureRuleSchemasLoaded();
426 // We remove the first parameter from the validation to give the user more 428 // We remove the first parameter from the validation to give the user more
427 // meaningful error messages. 429 // meaningful error messages.
428 validate([ruleIdentifiers, opt_cb], 430 validate([ruleIdentifiers, opt_cb],
429 ruleFunctionSchemas.removeRules.parameters.slice().splice(1)); 431 ruleFunctionSchemas.removeRules.parameters.slice().splice(1));
430 sendRequest("events.removeRules", 432 sendRequest("events.removeRules",
431 [this.eventName_, ruleIdentifiers, opt_cb], 433 [this.eventName_, ruleIdentifiers, opt_cb],
432 ruleFunctionSchemas.removeRules.parameters); 434 ruleFunctionSchemas.removeRules.parameters);
433 } 435 }
434 436
435 chrome.Event.prototype.getRules = function(ruleIdentifiers, cb) { 437 Event.prototype.getRules = function(ruleIdentifiers, cb) {
436 if (!this.eventOptions_.supportsRules) 438 if (!this.eventOptions_.supportsRules)
437 throw new Error("This event does not support rules."); 439 throw new Error("This event does not support rules.");
438 ensureRuleSchemasLoaded(); 440 ensureRuleSchemasLoaded();
439 // We remove the first parameter from the validation to give the user more 441 // We remove the first parameter from the validation to give the user more
440 // meaningful error messages. 442 // meaningful error messages.
441 validate([ruleIdentifiers, cb], 443 validate([ruleIdentifiers, cb],
442 ruleFunctionSchemas.getRules.parameters.slice().splice(1)); 444 ruleFunctionSchemas.getRules.parameters.slice().splice(1));
443 445
444 sendRequest("events.getRules", 446 sendRequest("events.getRules",
445 [this.eventName_, ruleIdentifiers, cb], 447 [this.eventName_, ruleIdentifiers, cb],
446 ruleFunctionSchemas.getRules.parameters); 448 ruleFunctionSchemas.getRules.parameters);
447 } 449 }
448 450
449 // Special load events: we don't use the DOM unload because that slows 451 // Special load events: we don't use the DOM unload because that slows
450 // down tab shutdown. On the other hand, onUnload might not always fire, 452 // down tab shutdown. On the other hand, onUnload might not always fire,
451 // since Chrome will terminate renderers on shutdown (SuddenTermination). 453 // since Chrome will terminate renderers on shutdown (SuddenTermination).
452 chromeHidden.onLoad = new chrome.Event(); 454 chromeHidden.onLoad = new Event();
453 chromeHidden.onUnload = new chrome.Event(); 455 chromeHidden.onUnload = new Event();
454 456
455 chromeHidden.dispatchOnLoad = 457 chromeHidden.dispatchOnLoad =
456 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad); 458 chromeHidden.onLoad.dispatch.bind(chromeHidden.onLoad);
457 459
458 chromeHidden.dispatchOnUnload = function() { 460 chromeHidden.dispatchOnUnload = function() {
459 chromeHidden.onUnload.dispatch(); 461 chromeHidden.onUnload.dispatch();
460 chromeHidden.wasUnloaded = true; 462 chromeHidden.wasUnloaded = true;
461 463
462 for (var i = 0; i < allAttachedEvents.length; ++i) { 464 for (var i = 0; i < allAttachedEvents.length; ++i) {
463 var event = allAttachedEvents[i]; 465 var event = allAttachedEvents[i];
464 if (event) 466 if (event)
465 event.detach_(); 467 event.detach_();
466 } 468 }
467 }; 469 };
468 470
469 chromeHidden.dispatchError = function(msg) { 471 chromeHidden.dispatchError = function(msg) {
470 console.error(msg); 472 console.error(msg);
471 }; 473 };
472 474
473 exports.Event = chrome.Event; 475 chrome.Event = Event;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698