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