// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. var chrome = chrome || {}; (function () { native function GetChromeHidden(); native function AttachEvent(eventName); native function DetachEvent(eventName); var chromeHidden = GetChromeHidden(); // Event object. If opt_eventName is provided, this object represents // the unique instance of that named event, and dispatching an event // with that name will route through this object's listeners. // // Example: // chrome.tabs.onChanged = new chrome.Event("tab-changed"); // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); // chromeHidden.Event.dispatch("tab-changed", "hi"); // will result in an alert dialog that says 'hi'. chrome.Event = function(opt_eventName, opt_argSchemas) { this.eventName_ = opt_eventName; this.listeners_ = []; // Validate event parameters if we are in debug. if (opt_argSchemas && chromeHidden.validateCallbacks && chromeHidden.validate) { this.validate_ = function(args) { try { chromeHidden.validate(args, opt_argSchemas); } catch (exception) { return "Event validation error during " + opt_eventName + " -- " + exception; } } } }; // A map of event names to the event object that is registered to that name. var attachedNamedEvents = {}; // An array of all attached event objects, used for detaching on unload. var allAttachedEvents = []; chromeHidden.Event = {}; // Dispatches a named event with the given JSON array, which is deserialized // before dispatch. The JSON array is the list of arguments that will be // sent with the event callback. chromeHidden.Event.dispatchJSON = function(name, args) { if (attachedNamedEvents[name]) { if (args) { args = JSON.parse(args); } return attachedNamedEvents[name].dispatch.apply( attachedNamedEvents[name], args); } }; // Dispatches a named event with the given arguments, supplied as an array. chromeHidden.Event.dispatch = function(name, args) { if (attachedNamedEvents[name]) { attachedNamedEvents[name].dispatch.apply( attachedNamedEvents[name], args); } }; // Test if a named event has any listeners. chromeHidden.Event.hasListener = function(name) { return (attachedNamedEvents[name] && attachedNamedEvents[name].listeners_.length > 0); } // Registers a callback to be called when this event is dispatched. chrome.Event.prototype.addListener = function(cb) { this.listeners_.push(cb); if (this.listeners_.length == 1) { this.attach_(); } }; // Unregisters a callback. chrome.Event.prototype.removeListener = function(cb) { var idx = this.findListener_(cb); if (idx == -1) { return; } this.listeners_.splice(idx, 1); if (this.listeners_.length == 0) { this.detach_(); } }; // Test if the given callback is registered for this event. chrome.Event.prototype.hasListener = function(cb) { return this.findListener_(cb) > -1; }; // Test if any callbacks are registered for this event. chrome.Event.prototype.hasListeners = function(cb) { return this.listeners_.length > 0; }; // Returns the index of the given callback if registered, or -1 if not // found. chrome.Event.prototype.findListener_ = function(cb) { for (var i = 0; i < this.listeners_.length; i++) { if (this.listeners_[i] == cb) { return i; } } return -1; }; // Dispatches this event object to all listeners, passing all supplied // arguments to this function each listener. chrome.Event.prototype.dispatch = function(varargs) { var args = Array.prototype.slice.call(arguments); if (this.validate_) { var validationErrors = this.validate_(args); if (validationErrors) { return validationErrors; } } for (var i = 0; i < this.listeners_.length; i++) { try { this.listeners_[i].apply(null, args); } catch (e) { console.error(e); } } }; // Attaches this event object to its name. Only one object can have a given // name. chrome.Event.prototype.attach_ = function() { AttachEvent(this.eventName_); allAttachedEvents[allAttachedEvents.length] = this; if (!this.eventName_) return; if (attachedNamedEvents[this.eventName_]) { throw new Error("chrome.Event '" + this.eventName_ + "' is already attached."); } attachedNamedEvents[this.eventName_] = this; }; // Detaches this event object from its name. chrome.Event.prototype.detach_ = function() { var i = allAttachedEvents.indexOf(this); if (i >= 0) delete allAttachedEvents[i]; DetachEvent(this.eventName_); if (!this.eventName_) return; if (!attachedNamedEvents[this.eventName_]) { throw new Error("chrome.Event '" + this.eventName_ + "' is not attached."); } delete attachedNamedEvents[this.eventName_]; }; // Special load events: we don't use the DOM unload because that slows // down tab shutdown. On the other hand, onUnload might not always fire, // since Chrome will terminate renderers on shutdown (SuddenTermination). chromeHidden.onLoad = new chrome.Event(); chromeHidden.onUnload = new chrome.Event(); chromeHidden.dispatchOnLoad = function(extensionId) { chromeHidden.onLoad.dispatch(extensionId); } chromeHidden.dispatchOnUnload = function() { chromeHidden.onUnload.dispatch(); for (var i = 0; i < allAttachedEvents.length; ++i) { var event = allAttachedEvents[i]; if (event) event.detach_(); } } chromeHidden.dispatchError = function(msg) { console.error(msg); } })();