// // BEGIN FLOCK GPL // // Copyright Flock Inc. 2005-2007 // http://flock.com // // This file may be used under the terms of of the // GNU General Public License Version 2 or later (the "GPL"), // http://www.gnu.org/licenses/gpl.html // // Software distributed under the License is distributed on an "AS IS" basis, // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License // for the specific language governing rights and limitations under the // License. // // END FLOCK GPL // // This code is based on the Javascript part of the proposed patch // to Mozilla found here: // // https://bugzilla.mozilla.org/show_bug.cgi?id=71689 // // Bugzilla Bug 71689 - Need helper utilities for JS component authors // "They suffer without a generic module implementation, and the // cutting-and-pasting breaks my little heart." (a-yup) // // All objects in here become properties on the 'FlockXPCOMUtils' object. const CC = Components.classes; const CI = Components.interfaces; const CR = Components.results; var EXPORTED_SYMBOLS = [ "FlockXPCOMUtils" ]; debug("*** loading FlockXPCOMUtils\n"); var FlockXPCOMUtils = { debug: false, /** * Creates an NSGetModule function that returns the result of calling * genericModule with the same parameters. */ generateNSGetModule: function FlockXPCOMUtils_generateNSGetModule(aName, aComponentsArray) { return function FlockXPCOMUtils_NSGetModule(aCompMgr, aFileSpec) { return new FlockXPCOMUtils.genericModule(aName, aComponentsArray); }; }, /** * Creates a generic Javascript XPCOM module to contain one or more XPCOM * components also written in Javascript. * * @param aName (in) A descriptive name for the module. Only used for * identifying purposes in debug messages. * @param aComponentsArray (in) * * @return An object that implements the methods required to participate in * XPCOM: registerSelf, unregisterSelf, getClassObject, canUnload. */ genericModule: function FlockXPCOMUtils_genericModule(aName, aComponentsArray) { // the first argument is the module name (used for debugging only) this.moduleName = aName; // and the second is an array of components this.classes = []; for each (let component in aComponentsArray) { this.classes.push(FlockXPCOMUtils.classDescForComponent(component)); } // use FlockXPCOMUtils as the default for printing debugging statements this.debug = FlockXPCOMUtils.debug; // override printDebug() to make messages slightly more informative this.printDebug = function genericModule_printDebug(message) { if (this.debug) { FlockXPCOMUtils.printDebug("FlockXPCOMUtils Mod: " + this.moduleName + ": " + message); } }; if (this.classes.length === 0) { this.printDebug("(WARNING) no components given to genericModule"); } this.registerSelf = function genericModule_registerSelf(aCompMgr, aFileSpec, aLocation, aType) { this.printDebug("entering registerSelf..."); var registrar = aCompMgr.QueryInterface(CI.nsIComponentRegistrar); for each (let classDesc in this.classes) { this.printDebug(" registering component '" + classDesc.className + "'"); registrar.registerFactoryLocation(classDesc.classID, classDesc.className, classDesc.contractID, aFileSpec, aLocation, aType); FlockXPCOMUtils.addCategories(classDesc); // callback to allow component specific registration classDesc.register(aCompMgr, aFileSpec, aLocation, aType); } this.printDebug("leaving registerSelf"); }; this.unregisterSelf = function genericModule_unregisterSelf(aCompMgr, aFileSpec, aLocation) { this.printDebug("entering unregisterSelf..."); var registrar = aCompMgr.QueryInterface(CI.nsIComponentRegistrar); for each (let classDesc in this.classes) { this.printDebug(" unregistering component '" + classDesc.className + "'"); // callback to allow classDesc specific unregistration classDesc.unregister(aCompMgr, aFileSpec, aLocation, aType); FlockXPCOMUtils.deleteCategories(classDesc); registrar.unregisterFactoryLocation(classDesc.classID, aFileSpec); } this.printDebug("leaving unregisterSelf"); }; this.getClassObject = function genericModule_getClassObject(aCompMgr, aCID, iid) { this.printDebug("getClassObject() called"); // XXX: Right now, we only support nsIFactory queries, not nsIClassInfo // However, we do provide an nsIClassInfo for components that use us // so perhaps we *could*. But I'm unsure about the semantics, and // there appears to be no users of that feature in the tree. -Manish if (!iid.equals(CI.nsIFactory)) { this.printDebug("(error) iid was '" + iid + "'"); throw CR.NS_ERROR_NOT_IMPLEMENTED; } for each (let classDesc in this.classes) { if (aCID.equals(classDesc.classID)) { this.printDebug(" ClassID MATCHES component '" + classDesc.className + "'"); return classDesc.factory; } this.printDebug(" ClassID does not match component '" + classDesc.className + "'"); } // no component had a matching contract id this.printDebug("(ERROR) unable to match cid='" + aCID + "'"); throw CR.NS_ERROR_FACTORY_NOT_REGISTERED; }; this.QueryInterface = function genericModule_QueryInterface(iid) { if (iid.equals(CI.nsIModule) || iid.equals(CI.nsISupports)) { return this; } throw CR.NS_ERROR_NO_INTERFACE; }; this.canUnload = function genericModule_canUnload(aCompMgr) { this.printDebug("canUnload called"); return true; // return value is ignored for javascript components }; }, /** * Creates a generic Javascript XPCOM component with a set of predefined * interfaces: nsISupports, nsISupportsPrimitive, nsISupportsCString, nsIClassInfo. * * @param aName (in) A descriptive name for the component. Becomes the * nsIClassInfo property "classDescription" * @param aCID (in) The ClassID for this component. * @param aContractID (in) The ContractID for this component. * @param aCtor (in) The constuctor for the object. Usage: return new aCtor(); * @param aClassFlags (in) Class from nsIClassInfo flags ORed together. To make * your component a singleton, * use Components.interfaces.nsIClassInfo.SINGLETON. * @param aInterfaceArray (in) The list of interfaces the component supports. * Do not list any of the predefined interfaces here. * Example: * [Components.interfaces.flockILoggingService, Components.interfaces.nsIObserver] * @return An object that implements a class factory for the listed interfaces, and * can be QueryInterfaced()'ed for any of the supported interfaces. * * @see nsIClassInfo */ genericComponent: function FlockXPCOMUtils_genericComponent(aName, aCID, aContractID, aCtor, aClassFlags, aInterfaceArray) { // We need to use "this" as it was during component creation, so we // take a copy now. Within genericComponent(), references to "this" // are from the context of the caller of the defined function. var inst = this; inst.ctor = aCtor; // For creating new instances inst.classDescription = aName; // Property of nsIClassInfo interface // use FlockXPCOMUtils as the default for printing debugging statements inst.showdebug = FlockXPCOMUtils.debug; // override printDebug() to make messages slightly more informative inst.printDebug = function genericComponent_printDebug(message) { if (inst.showdebug) { FlockXPCOMUtils.printDebug("FlockXPCOMUtils Cmp: " +inst.classDescription+ ": " + message); } }; // Build the list of interfaces supported by the component. inst.interfaceArray = []; // All components have nsISupports, so add it automatically. inst.interfaceArray.push(Components.interfaces.nsISupports); // We provide nsIClassInfo for components using this module. inst.interfaceArray.push(Components.interfaces.nsIClassInfo); inst.predefinedInterfaceCount = inst.interfaceArray.length; // Record the interfaces supported by the component. for (var i = 0; i < aInterfaceArray.length; i++) { inst.interfaceArray.push(aInterfaceArray[i]); } // Check that the caller actually gave an interface to use if (inst.interfaceArray.length === inst.predefinedInterfaceCount) { inst.printDebug("(WARNING) no user specified interfaces"); } // warn if it is obvious something else has been passed instead of a CID // (the raw xpconnect error is obscure enough to warrant a warning) if (typeof(aCID) !== "object") { inst.printDebug("(WARNING) cid '" + aCID + "' is not an object!"); } // register() is a callback from genericModule.registerSelf() inst.register = function genericComponent_register(aCompMgr, aFileSpec, aLocation, aType) { inst.printDebug("register called on component '" + this.classDescription + "'"); return; // user overrides 'register()' for component specific registration }; // unregister() is a callback from genericModule.unregisterSelf() inst.unregister = function genericComponent_unregister(aCompMgr, aFileSpec, aLocation, aType) { inst.printDebug("unregister called on component '" + this.classDescription + "'"); return; // user can override for component specific unregistration }; // Convenience wrapper around FlockXPCOMUtils.addCategories inst.addCategories = function genericComponent_addCategories() { inst.printDebug("instance manually adding categories"); var classDesc = FlockXPCOMUtils.classDescForComponent(inst.ctor); FlockXPCOMUtils.addCategories(classDesc); }; // Convenience wrapper around FlockXPCOMUtils.deleteCategories inst.deleteCategories = function genericComponent_deleteCategories() { inst.printDebug("instance manually deleting categories"); var classDesc = FlockXPCOMUtils.classDescForComponent(inst.ctor); FlockXPCOMUtils.deleteCategories(classDesc); }; // nsISupports interface inst.QueryInterface = function genericComponent_QueryInterface(iid) { inst.printDebug("QueryInterface called"); // see if component object supports the requested interface for (i = 0; i < inst.interfaceArray.length; i++) { if (iid.equals(inst.interfaceArray[i])) { inst.printDebug(" MATCHED interface '" + inst.interfaceArray[i] + "'"); return this; } } if (inst.showdebug) { // expensive but rare check to get better debug message for (var name in Components.interfaces) { if (iid.equals(Components.interfaces[name])) { inst.printDebug(" NS_ERROR_NO_INTERFACE on QI for '" + name + "'"); throw Components.results.NS_ERROR_NO_INTERFACE; } } inst.printDebug(" NS_ERROR_NO_INTERFACE on QI for '" + iid + "'"); } throw Components.results.NS_ERROR_NO_INTERFACE; }; // nsIClassInfo interface inst.getInterfaces = function genericComponent_getInterfaces(aCount) { var iids = []; for (i = 0; i < inst.interfaceArray.length; i++) { iids.push(inst.interfaceArray[i]); } aCount.value = iids.length; return iids; }; inst.getHelperForLanguage = function genericComponent_getHelperForLanguage(aLanguage) { return null; }; inst.contractID = aContractID; inst.classID = aCID; inst.implementationLanguage = Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT; // See xpcom/components/nsIClassInfo.idl for more flags. inst.flags = aClassFlags; // required by nsIFactory so define to prevent warnings inst.lockFactory = function genericComponent_lockFactory(lock) { inst.printDebug("lockFactory() called"); return true; }; // nsIFactory function pointed to by module.getClassObject() inst.createInstance = function genericComponent_createInstance(outer, iid) { inst.printDebug("createInstance called for " + inst.classDescription); if (outer) { /* FIXME: tell me again why don't we handle aggregation? */ inst.printDebug("(ERROR) interface does not allow aggregation"); throw Components.results.NS_ERROR_NO_AGGREGATION; } inst.printDebug(" creating new instance and querying for interface"); return (new inst.ctor()).QueryInterface(iid); }; }, // helper function to add an entry to the category manager addCategoryEntry: function FlockXPCOMUtils_addCategoryEntry(category, name, value) { this.printDebug("FlockXPCOMUtils addCategory: adding '" + name + "' to category '" + category + "'"); /* NOTE: different categories use different entries for name and value */ this.categoryManager.addCategoryEntry(category, name, value, true, true); }, // helper function to remove an entry from the category manager deleteCategoryEntry: function FlockXPCOMUtils_deleteCategoryEntry(category, name) { this.printDebug("FlockXPCOMUtils deleteCategory: deleting '" + name + "' from category '" + category + "'"); this.categoryManager.deleteCategoryEntry(category, name, true); }, // helper function add categories to the category manager out of // component.prototype._xpcom_categories addCategories: function FlockXPCOMUtils_addCategories(classDesc) { if (classDesc.categories) { for each (let cat in classDesc.categories) { let defaultValue = (cat.service ? "service," : "") + classDesc.contractID; this.addCategoryEntry(cat.category, cat.entry || classDesc.className, cat.value || defaultValue); } } }, // helper function delete categories to the category manager out of // component.prototype._xpcom_categories deleteCategories: function FlockXPCOMUtils_deleteCategories(classDesc) { if (classDesc.categories) { for each (let cat in classDesc.categories) { this.deleteCategoryEntry(cat.category, cat.entry || classDesc.className); } } }, classDescForComponent: function FlockXPCOMUtils_classDescForComponent(aComponent) { return { classID: aComponent.prototype.classID, className: aComponent.prototype.classDescription, contractID: aComponent.prototype.contractID, factory: aComponent.prototype, // component acts as its own nsIFactory categories: aComponent.prototype._xpcom_categories, register: aComponent.prototype.register, unregister: aComponent.prototype.unregister }; }, get categoryManager() { return CC["@mozilla.org/categorymanager;1"] .getService(CI.nsICategoryManager); }, // print a debug message (turn on and off via FlockXPCOMUtils.debug) printDebug: function FlockXPCOMUtils_printDebug(message) { if (this.debug) dump(message + "\n"); } }; // var FlockXPCOMUtils