home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February / PCWorld_2008-02_cd.bin / temacd / songbird / Songbird_0.4_windows-i686.exe / components / sbFeathersManager.js < prev    next >
Text File  |  2007-12-21  |  41KB  |  1,364 lines

  1. /**
  2. //
  3. // BEGIN SONGBIRD GPL
  4. // 
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 POTI, Inc.
  8. // http://songbirdnest.com
  9. // 
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. // 
  13. // Software distributed under the License is distributed 
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either 
  15. // express or implied. See the GPL for the specific language 
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this 
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc., 
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. // 
  23. // END SONGBIRD GPL
  24. //
  25.  */
  26.  
  27. /**
  28.  * \file sbFeathersManager.js
  29.  * \brief Coordinates the loading of feathers (combination of skin and XUL window layout)
  30.  */ 
  31.  
  32.  
  33. //
  34. // TODO:
  35. //  * add onSwitchCompleted, change onSwitchRequested to allow feedback
  36. //  * Explore skin/layout versioning issues?
  37. // 
  38.  
  39. const CONTRACTID = "@songbirdnest.com/songbird/feathersmanager;1";
  40. const CLASSNAME = "Songbird Feathers Manager Service Interface";
  41. const CID = Components.ID("{99f24350-a67f-11db-befa-0800200c9a66}");
  42. const IID = Components.interfaces.sbIFeathersManager;
  43.  
  44.  
  45. const RDFURI_ADDON_ROOT               = "urn:songbird:addon:root" 
  46. const PREFIX_NS_SONGBIRD              = "http://www.songbirdnest.com/2007/addon-metadata-rdf#";
  47.  
  48. // Fallback layouts/skin, used by previousSkinName and previousLayoutURL
  49. // Changes to the shipped feathers must be reflected here
  50. // and in test_feathersManager.js
  51. const DEFAULT_MAIN_LAYOUT_URL         = "chrome://songbird/content/feathers/basic-layouts/xul/mainplayer.xul";
  52. const DEFAULT_SECONDARY_LAYOUT_URL    = "chrome://songbird/content/feathers/basic-layouts/xul/miniplayer.xul";
  53. const DEFAULT_SKIN_NAME               = "rubberducky/0.2";
  54.  
  55. const WINDOWTYPE_SONGBIRD_PLAYER      = "Songbird:Main";
  56. const WINDOWTYPE_SONGBIRD_CORE        = "Songbird:Core";
  57.  
  58.  
  59.  
  60.  
  61. /**
  62.  * /class ArrayEnumerator
  63.  * /brief Converts a js array into an nsISimpleEnumerator
  64.  */
  65. function ArrayEnumerator(array) 
  66. {
  67.   this.data = array;
  68. }
  69. ArrayEnumerator.prototype = {
  70.  
  71.   index: 0,
  72.   
  73.   getNext: function() {
  74.     return this.data[this.index++];
  75.   },
  76.   
  77.   hasMoreElements: function() {
  78.     if (this.index < this.data.length)
  79.       return true;
  80.     else
  81.       return false;
  82.   },
  83.   
  84.   QueryInterface: function(iid)
  85.   {
  86.     if (!iid.equals(Components.interfaces.nsISimpleEnumerator) &&
  87.         !iid.equals(Components.interfaces.nsISupports))
  88.       throw Components.results.NS_ERROR_NO_INTERFACE;
  89.     return this;
  90.   }
  91. }
  92.  
  93.  
  94.  
  95. /**
  96.  * Debug helper that serializes an RDF datasource to the console
  97.  */
  98. function dumpDS(prefix, ds) {
  99.   var outputStream = {
  100.     data: "",
  101.     close : function(){},
  102.     flush : function(){},
  103.     write : function (buffer,count){
  104.       this.data += buffer;
  105.       return count;
  106.     },
  107.     writeFrom : function (stream,count){},
  108.     isNonBlocking: false
  109.   }
  110.  
  111.   var serializer = Components.classes["@mozilla.org/rdf/xml-serializer;1"]
  112.                            .createInstance(Components.interfaces.nsIRDFXMLSerializer);
  113.   serializer.init(ds);
  114.  
  115.   serializer.QueryInterface(Components.interfaces.nsIRDFXMLSource);
  116.   serializer.Serialize(outputStream);
  117.   
  118.   outputStream.data.split('\n').forEach( function(line) {
  119.     dump(prefix + line + "\n");
  120.   });
  121. }
  122.  
  123.  
  124.  
  125.  
  126. /**
  127.  * sbISkinDescription
  128.  */
  129. function SkinDescription() {};
  130. SkinDescription.prototype = {
  131.   // TODO Expand?
  132.   requiredProperties: [ "name", "internalName" ],
  133.   optionalProperties: [ ],
  134.   
  135.   QueryInterface: function(iid) {
  136.     if (!iid.equals(Components.interfaces.sbISkinDescription)) 
  137.       throw Components.results.NS_ERROR_NO_INTERFACE;
  138.     return this;
  139.   }
  140. };
  141.  
  142. /**
  143.  * sbILayoutDescription
  144.  */
  145. function LayoutDescription() {};
  146. LayoutDescription.prototype = {
  147.   // TODO Expand?
  148.   requiredProperties: [ "name", "url" ],
  149.   optionalProperties: [ ],
  150.   
  151.   QueryInterface: function(iid) {
  152.     if (!iid.equals(Components.interfaces.sbILayoutDescription)) 
  153.       throw Components.results.NS_ERROR_NO_INTERFACE;
  154.     return this;
  155.   }
  156. };
  157.  
  158.  
  159. /**
  160.  * Static function that verifies the contents of the given description
  161.  *
  162.  * Example:
  163.  *   try {
  164.  *     LayoutDescription.verify(layout);
  165.  *   } catch (e) {
  166.  *     reportError(e);
  167.  *   }
  168.  *
  169.  */
  170. LayoutDescription.verify = SkinDescription.verify = function( description ) 
  171. {
  172.   for (var i = 0; i < this.prototype.requiredProperties.length; i++) {
  173.     var property = this.prototype.requiredProperties[i];
  174.     if (! (typeof(description[property]) == 'string'
  175.              && description[property].length > 0)) 
  176.     {
  177.       throw("Invalid description. '" + property + "' is a required property.");
  178.     }
  179.   }
  180. }
  181.  
  182.  
  183.  
  184.  
  185.  
  186.  
  187. /**
  188.  * /class AddonMetadataReader
  189.  * Responsible for reading addon metadata and performing 
  190.  * registration with FeathersManager
  191.  */
  192. function AddonMetadataReader() {
  193.   //debug("AddonMetadataReader: ctor\n");
  194.   this._RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
  195.                         .getService(Components.interfaces.nsIRDFService);
  196.   this._datasource = this._RDF.GetDataSourceBlocking("rdf:addon-metadata");
  197.   this._feathersManager = Components.classes[CONTRACTID].getService(IID);
  198.     
  199.   this._resources = {
  200.     root: this._RDF.GetResource(RDFURI_ADDON_ROOT),
  201.     // Helper to convert a string array into 
  202.     // RDF resources in this object
  203.     addSongbirdResources: function(list){
  204.       for (var i = 0; i < list.length; i++) {
  205.         this[list[i]] = this._RDF.GetResource(PREFIX_NS_SONGBIRD + list[i]);
  206.       }
  207.     },
  208.     _RDF: this._RDF
  209.   };
  210.   
  211.   // Make RDF resources for all properties expected
  212.   // in the feathers portion of an install.rdf      
  213.   this._resources.addSongbirdResources(SkinDescription.prototype.requiredProperties);
  214.   this._resources.addSongbirdResources(SkinDescription.prototype.optionalProperties);
  215.   this._resources.addSongbirdResources(LayoutDescription.prototype.requiredProperties);
  216.   this._resources.addSongbirdResources(LayoutDescription.prototype.optionalProperties);
  217.   this._resources.addSongbirdResources(
  218.      [ "compatibleSkin", 
  219.        "compatibleLayout", 
  220.        "showChrome",
  221.        "feathers",
  222.        "skin",
  223.        "layout",
  224.        "layoutURL",
  225.        "onTop" ] );
  226. }
  227. AddonMetadataReader.prototype = {
  228.  
  229.   _RDF: null,
  230.   
  231.   _feathersManager: null,
  232.  
  233.   // Addon metadata rdf datasource
  234.   _datasource: null,
  235.  
  236.   // Hash of addon metadata RDF resources
  237.   _resources: null,
  238.  
  239.   /**
  240.    * Populate FeathersManager using addon metadata
  241.    */
  242.   loadFeathers: function loadFeathers() {
  243.     //debug("AddonMetadataReader: loadFeathers\n");
  244.     
  245.     // Get addon list
  246.     var containerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"]
  247.                            .getService(Components.interfaces.nsIRDFContainerUtils);
  248.     var container = containerUtils.MakeSeq(this._datasource, this._resources.root);
  249.     var addons = container.GetElements();
  250.     
  251.     // Search all addons for feathers metadata
  252.     while (addons.hasMoreElements()) {
  253.       var addon = addons.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  254.       //debug("AddonMetadataReader.loadFeathers: - processing " + addon.Value + "\n");
  255.       try {
  256.       
  257.         if (this._datasource.hasArcOut(addon, this._resources.feathers)) {
  258.           var feathersTarget = this._datasource.GetTarget(addon, this._resources.feathers, true)
  259.                                    .QueryInterface(Components.interfaces.nsIRDFResource);
  260.           
  261.           // Process all skin metadata
  262.           var items = this._datasource.GetTargets(feathersTarget, this._resources.skin, true)
  263.           while (items.hasMoreElements()) {
  264.             var item = items.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  265.             this._processSkin(addon, item);
  266.           }
  267.  
  268.           // Process all layout metadata
  269.           var items = this._datasource.GetTargets(feathersTarget, this._resources.layout, true)
  270.           while (items.hasMoreElements()) {
  271.             var item = items.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  272.             this._processLayout(addon, item);
  273.           }
  274.         }
  275.         
  276.       } catch (e) {
  277.         this._reportErrors("", [  "An error occurred while processing " +
  278.                     "extension " + addon.Value + ".  Exception: " + e  ]);
  279.       }
  280.     }
  281.   },
  282.   
  283.   
  284.   /**
  285.    * Extract skin metadata
  286.    */
  287.   _processSkin: function _processSkin(addon, skin) {
  288.     var description = new SkinDescription();
  289.     
  290.     // Array of error messages
  291.     var errorList = [];
  292.     
  293.     // Fill the description object
  294.     this._populateDescription(skin, description, errorList);
  295.     
  296.     try {
  297.       SkinDescription.verify(description);
  298.     } catch (e) {
  299.       errorList.push(e.toString());
  300.     }
  301.     
  302.     // If errors were encountered, then do not submit 
  303.     // to the Feathers Manager
  304.     if (errorList.length > 0) {
  305.       this._reportErrors(
  306.           "Ignoring skin addon in the install.rdf of extension " +
  307.           addon.Value + ". Message: ", errorList);
  308.       return;
  309.     }
  310.     
  311.     // Submit description
  312.     this._feathersManager.registerSkin(description);
  313.     //debug("AddonMetadataReader: registered skin " + description.internalName
  314.     //        + " from addon " + addon.Value + " \n");
  315.     
  316.     // Get compatibility information
  317.     var identifiers, showChromeInstructions, onTopInstructions;
  318.     [identifiers, showChromeInstructions, onTopInstructions] =
  319.         this._getCompatibility(addon, skin, "compatibleLayout", "layoutURL", errorList);
  320.  
  321.     // Report errors
  322.     if (errorList.length > 0) {
  323.       this._reportErrors(
  324.           "Error finding compatibility information for skin " +
  325.           description.name + " in the install.rdf " +
  326.           "of extension " + addon.Value + ". Message: ", errorList);
  327.     }
  328.      
  329.     // Assert compatibility    
  330.     for (var i = 0; i < identifiers.length; i++) {
  331.       this._feathersManager.assertCompatibility(
  332.                identifiers[i],
  333.                description.internalName, 
  334.                showChromeInstructions[i],
  335.                onTopInstructions[i]);
  336.     }
  337.   },
  338.  
  339.  
  340.   /**
  341.    * Extract layout metadata
  342.    */
  343.   _processLayout: function _processLayout(addon, layout) {
  344.     var description = new LayoutDescription();
  345.     
  346.     // Array of error messages
  347.     var errorList = [];
  348.     
  349.     // Fill the description object
  350.     this._populateDescription(layout, description, errorList);
  351.     
  352.     try {
  353.       LayoutDescription.verify(description);
  354.     } catch (e) {
  355.       errorList.push(e.toString());
  356.     }
  357.     
  358.     // If errors were encountered, then do not submit 
  359.     // to the Feathers Manager
  360.     if (errorList.length > 0) {
  361.       this._reportErrors(
  362.           "Ignoring layout addon in the install.rdf of extension " +
  363.           addon.Value + ". Message: ", errorList);
  364.       return;
  365.     }
  366.     
  367.     // Submit description
  368.     this._feathersManager.registerLayout(description);
  369.     //debug("AddonMetadataReader: registered layout " + description.name +
  370.     //     " from addon " + addon.Value + "\n");    
  371.     
  372.     // Get compatibility information
  373.     var identifiers, showChromeInstructions, onTopInstructions;
  374.     [identifiers, showChromeInstructions, onTopInstructions] =
  375.         this._getCompatibility(addon, layout, "compatibleSkin", "internalName", errorList);
  376.  
  377.     // Report errors
  378.     if (errorList.length > 0) {
  379.       this._reportErrors(
  380.           "Error finding compatibility information for layout " +
  381.           description.name + " in the install.rdf " +
  382.           "of extension " + addon.Value + ". Message: ", errorList);
  383.     }
  384.     
  385.     // Assert compatibility      
  386.     for (var i = 0; i < identifiers.length; i++) {
  387.       this._feathersManager.assertCompatibility(
  388.                description.url,
  389.                identifiers[i], 
  390.                showChromeInstructions[i],
  391.                onTopInstructions[i]);
  392.     }
  393.   
  394.   },
  395.  
  396.  
  397.  
  398.   /**
  399.    * \brief Populate a description object by looking up requiredProperties and
  400.    *        optionalProperties in a the given rdf source.
  401.    *
  402.    * \param source RDF resource from which to obtain property values
  403.    * \param description Object with requiredProperties and optionalProperties arrays
  404.    * \param errorList An array to which error messages should be added
  405.    */
  406.   _populateDescription: function _populateDescription(source, description, errorList) {
  407.  
  408.     for (var i = 0; i < description.requiredProperties.length; i++) {
  409.       this._requireProperty(source, description, 
  410.                 description.requiredProperties[i], errorList);
  411.     }
  412.     for (var i = 0; i < description.optionalProperties.length; i++) {
  413.       this._copyProperty(source, description, 
  414.                 description.optionalProperties[i], errorList);
  415.     }
  416.   },
  417.   
  418.   
  419.   /**
  420.    * \brief Attempts to copy a property from an RDFResource into a
  421.    *        container object and reports an error on failure.
  422.    *
  423.    * \param source RDF resource from which to obtain the value
  424.    * \param description Container object to receive the value
  425.    * \param property String property to be copied
  426.    * \param errorList An array to which error messages should be added
  427.    */
  428.   _requireProperty: function _requireProperty(source, description, property, errorList) {
  429.     this._copyProperty(source, description, property);
  430.     if (description[property] == undefined) 
  431.     {
  432.       errorList.push(
  433.         property + " is a required property."
  434.       );
  435.     }
  436.   },
  437.  
  438.   /**
  439.    * \brief Copies a property from an RDFResource into a container object.
  440.    *
  441.    * \param source RDF resource from which to obtain the value
  442.    * \param description Container object to receive the value
  443.    * \param property String property to be copied
  444.    */
  445.   _copyProperty: function _copyProperty(source, description, property) {
  446.     description[property] = this._getProperty(source, property);
  447.   },
  448.  
  449.  
  450.   /**
  451.    * \brief Copies a property from an RDFResource into a container object.
  452.    *
  453.    * \param source RDF resource from which to obtain the value
  454.    * \param description Container object to receive the value
  455.    * \param property String property to be copied
  456.    */  
  457.   _getProperty: function _getProperty(source, property) {
  458.     //debug("AddonMetadataReader._getProperty " + source.Value + " " + property + "\n");
  459.     var target = this._datasource.GetTarget(source, this._resources[property], true);
  460.     if ( target instanceof Components.interfaces.nsIRDFInt
  461.          || target instanceof Components.interfaces.nsIRDFLiteral 
  462.          || target instanceof Components.interfaces.nsIRDFResource )
  463.     {
  464.       return target.Value;
  465.     }
  466.     return undefined;
  467.   },
  468.  
  469.  
  470.   /**
  471.    * \brief Extracts compatibility information for a feathers item into 
  472.    *        an array of identifiers and matching array of booleans 
  473.    *        indicating whether chrome should be shown
  474.    *
  475.    * \param addon RDF resource from the addon description
  476.    * \param source RDF resource for the feathers item
  477.    * \param resourceName Container object to receive the value
  478.    * \param idProperty String property to be copied
  479.    * \param errorList An array to which error messages should be added
  480.    * \return [array of identifiers, array of showChrome bools]
  481.    */
  482.   _getCompatibility: function _getCompatibility(addon, source, resourceName, idProperty, errorList) {
  483.     // List of internalName or layoutURL identifiers
  484.     var identifiers = [];
  485.     // Matching list of showChrome hints
  486.     var showChromeInstructions = [];
  487.     // Matching list of onTop hints
  488.     var onTopInstructions = [];
  489.     
  490.     // Look at all compatibility rules of type resourceName
  491.     var targets = this._datasource.GetTargets(source, this._resources[resourceName], true);
  492.     while (targets.hasMoreElements()) {
  493.       var target = targets.getNext();
  494.       
  495.       // Target must be a resource / contain a description
  496.       if (! (target instanceof Components.interfaces.nsIRDFResource)) {
  497.         
  498.         errorList.push("The install.rdf for " + addon.Value 
  499.               + " is providing an incomplete " + resourceName 
  500.               + " section.");
  501.               
  502.         // Skip this section
  503.         continue;        
  504.       }
  505.       target = target.QueryInterface(Components.interfaces.nsIRDFResource);
  506.       
  507.       // Get the identifier for the compatible feather item
  508.       var id = this._getProperty(target, idProperty);
  509.       
  510.       // Must provide an identifier
  511.       if (id == undefined) {
  512.         errorList.push("Extension " + addon.Value 
  513.               + " must provide " + idProperty 
  514.               + " in " + resourceName + " descriptions");
  515.               
  516.         // Skip this rule since it is incomplete
  517.         continue;
  518.       }
  519.       
  520.       // Should chrome be shown with this id?
  521.       var showChrome = this._getProperty(target, "showChrome") == "true";
  522.  
  523.       // Should popup=yes be used with this id?
  524.       var onTop = this._getProperty(target, "onTop") == "true";
  525.       
  526.       // Store compatibility rule
  527.       identifiers.push(id);
  528.       showChromeInstructions.push(showChrome);
  529.       onTopInstructions.push(onTop);
  530.     }
  531.     
  532.     //debug("AddonMetadataReader: found " + identifiers.length + " " 
  533.     //      + resourceName + " rule(s) for addon " 
  534.     //      + addon.Value + "\n");
  535.  
  536.     return [identifiers, showChromeInstructions, onTopInstructions];
  537.   },
  538.   
  539.   
  540.   /**
  541.    * \brief Dump a list of errors to the console and jsconsole
  542.    *
  543.    * \param contextMessage Additional prefix to use before every line
  544.    * \param errorList Array of error messages
  545.    */
  546.   _reportErrors: function _reportErrors(contextMessage, errorList) {
  547.     var consoleService = Components.classes["@mozilla.org/consoleservice;1"].
  548.          getService(Components.interfaces.nsIConsoleService);
  549.     for (var i = 0; i  < errorList.length; i++) {
  550.       consoleService.logStringMessage("Feathers Metadata Reader: " 
  551.                                        + contextMessage + errorList[i]);
  552.       dump("FeathersMetadataReader: " + contextMessage + errorList[i] + "\n");
  553.     }
  554.   }
  555. }
  556.  
  557.  
  558.  
  559.  
  560.  
  561.  
  562.  
  563.  
  564.  
  565.  
  566.  
  567. /**
  568.  * /class FeathersManager
  569.  * /brief Coordinates the loading of feathers
  570.  *
  571.  * Acts as a registry for skins and layout (known as feathers)
  572.  * and manages compatibility and selection.
  573.  *
  574.  * \sa sbIFeathersManager
  575.  */
  576. function FeathersManager() {
  577.  
  578.   var os      = Components.classes["@mozilla.org/observer-service;1"]
  579.                       .getService(Components.interfaces.nsIObserverService);
  580.   // We need to unhook things on shutdown
  581.   os.addObserver(this, "quit-application", false);
  582.   
  583.   this._skins = {};
  584.   this._layouts = {};
  585.   this._mappings = {};
  586.   this._listeners = [];
  587. };
  588. FeathersManager.prototype = {
  589.   constructor: FeathersManager,
  590.   
  591.   _layoutDataRemote: null,
  592.   _skinDataRemote: null,
  593.  
  594.   _previousLayoutDataRemote: null,
  595.   _previousSkinDataRemote: null,
  596.   
  597.   _showChromeDataRemote: null,
  598.  
  599.   _switching: false,
  600.  
  601.   
  602.   // Hash of skin descriptions keyed by internalName (e.g. classic/1.0)
  603.   _skins: null,
  604.   
  605.   // Hash of layout descriptions keyed by URL
  606.   _layouts: null,
  607.   
  608.   
  609.   // Hash of layout URL to hash of compatible skin internalNames, pointing to 
  610.   // {showChrome,onTop} objects.  
  611.   //
  612.   // eg
  613.   // {  
  614.   //     mainwin.xul: {
  615.   //       blueskin: {showChrome:true, onTop:false},
  616.   //       redskin: {showChrome:false, onTop:true},
  617.   //     }
  618.   // }
  619.   //
  620.   // Compatibility is determined by whether or not a internalName
  621.   // key is *defined* in the hash, not the actual value it points to.
  622.   _mappings: null,
  623.   
  624.   
  625.   // Array of sbIFeathersChangeListeners
  626.   _listeners: null,
  627.  
  628.   _layoutCount: 0,
  629.   _skinCount: 0,
  630.   
  631.   _initialized: false,
  632.   
  633.  
  634.   /**
  635.    * Initializes dataremotes and triggers the AddonMetadataReader
  636.    * to explore installed extensions and register any feathers.
  637.    *
  638.    * Note that this function is not run until a get method is called
  639.    * on the feathers manager.  This is to defer loading the metadata
  640.    * as long as possible and avoid impacting startup time.
  641.    * 
  642.    */
  643.   _init: function init() {
  644.   
  645.     // If already initialized do nothing
  646.     if (this._initialized) {
  647.       return;
  648.     }
  649.     
  650.     // Make dataremotes to persist feathers settings
  651.     var createDataRemote =  new Components.Constructor(
  652.                   "@songbirdnest.com/Songbird/DataRemote;1",
  653.                   Components.interfaces.sbIDataRemote, "init");
  654.  
  655.     this._layoutDataRemote = createDataRemote("feathers.selectedLayout", null);
  656.     this._skinDataRemote = createDataRemote("selectedSkin", "general.skins.");
  657.     
  658.     this._previousLayoutDataRemote = createDataRemote("feathers.previousLayout", null);
  659.     this._previousSkinDataRemote = createDataRemote("feathers.previousSkin", null);
  660.     
  661.     // TODO: Rename accessibility.enabled?
  662.     this._showChromeDataRemote = createDataRemote("accessibility.enabled", null);
  663.     
  664.     // Load the feathers metadata
  665.     var metadataReader = new AddonMetadataReader();
  666.     metadataReader.loadFeathers();
  667.     
  668.     // If no layout url has been specified, set to default
  669.     if (this._layoutDataRemote.stringValue == "") {
  670.       this._layoutDataRemote.stringValue = DEFAULT_MAIN_LAYOUT_URL;
  671.     }
  672.     
  673.     this._initialized = true;
  674.   },
  675.   
  676.   /**
  677.    * Called on xpcom-shutdown
  678.    */
  679.   _deinit: function deinit() {
  680.     this._skins = null;
  681.     this._layouts = null;
  682.     this._mappings = null;
  683.     this._listeners = null;
  684.     this._layoutDataRemote = null;
  685.     this._skinDataRemote = null;
  686.     this._previousLayoutDataRemote = null;
  687.     this._previousSkinDataRemote = null;
  688.     this._showChromeDataRemote = null;
  689.   },
  690.     
  691.   /**
  692.    * \sa sbIFeathersManager
  693.    */
  694.   get currentSkinName() {
  695.     this._init();
  696.     return this._skinDataRemote.stringValue;
  697.   },
  698.   
  699.   
  700.   /**
  701.    * \sa sbIFeathersManager
  702.    */  
  703.   get currentLayoutURL() {
  704.     this._init();
  705.     return this._layoutDataRemote.stringValue;
  706.   },
  707.   
  708.   
  709.   /**
  710.    * \sa sbIFeathersManager
  711.    */  
  712.   get previousSkinName() {
  713.     this._init();
  714.     
  715.     // Test to make sure the previous skin exists
  716.     var skin = this.getSkinDescription(this._previousSkinDataRemote.stringValue);
  717.     
  718.     // If the skin exists, then return the skin name
  719.     if (skin) {
  720.       return skin.internalName;
  721.     }
  722.     
  723.     // Otherwise, return the default skin
  724.     return DEFAULT_SKIN_NAME;
  725.   },
  726.   
  727.   
  728.   /**
  729.    * \sa sbIFeathersManager
  730.    */  
  731.   get previousLayoutURL() {
  732.     this._init();
  733.     
  734.     // Test to make sure the previous layout exists
  735.     var layout = this.getLayoutDescription(this._previousLayoutDataRemote.stringValue);
  736.     
  737.     // If the layout exists, then return the url/identifier
  738.     if (layout) {
  739.       return layout.url;
  740.     }
  741.     
  742.     // Otherwise, return the default 
  743.     
  744.     // Use the main default unless it is currently
  745.     // active. This way if the user reverts for the
  746.     // first time they will end up in the miniplayer.
  747.     var layoutURL = DEFAULT_MAIN_LAYOUT_URL;
  748.     if (this.currentLayoutURL == layoutURL) {
  749.       layoutURL = DEFAULT_SECONDARY_LAYOUT_URL;
  750.     }
  751.     
  752.     return layoutURL;    
  753.   },
  754.  
  755.  
  756.   /**
  757.    * \sa sbIFeathersManager
  758.    */ 
  759.   get skinCount() {
  760.     this._init();
  761.     return this._skinCount;
  762.   },
  763.   
  764.   /**
  765.    * \sa sbIFeathersManager
  766.    */  
  767.   get layoutCount() {
  768.     this._init();
  769.     return this._layoutCount;
  770.   },
  771.  
  772.  
  773.   /**
  774.    * \sa sbIFeathersManager
  775.    */
  776.   getSkinDescriptions: function getSkinDescriptions() {
  777.     this._init();      
  778.     // Copy all the descriptions into an array, and then return an enumerator
  779.     return new ArrayEnumerator( [this._skins[key] for (key in this._skins)] );
  780.   },
  781.  
  782.   /**
  783.    * \sa sbIFeathersManager
  784.    */
  785.   getLayoutDescriptions: function getLayoutDescriptions() {
  786.     this._init();        
  787.     // Copy all the descriptions into an array, and then return an enumerator
  788.     return new ArrayEnumerator( [this._layouts[key] for (key in this._layouts)] );
  789.   },
  790.   
  791.   
  792.   /**
  793.    * \sa sbIFeathersManager
  794.    */  
  795.   registerSkin: function registerSkin(skinDesc) {
  796.     SkinDescription.verify(skinDesc);
  797.     
  798.     if (this._skins[skinDesc.internalName] == null) {
  799.       this._skinCount++;
  800.     }
  801.     this._skins[skinDesc.internalName] = skinDesc;
  802.     
  803.     // Notify observers
  804.     this._onUpdate();
  805.   },
  806.  
  807.   /**
  808.    * \sa sbIFeathersManager
  809.    */
  810.   unregisterSkin: function unregisterSkin(skinDesc) {
  811.     if (this._skins[skinDesc.internalName]) {
  812.       delete this._skins[skinDesc.internalName];
  813.       this._skinCount--;
  814.       
  815.       // Notify observers
  816.       this._onUpdate();
  817.     }
  818.   },
  819.  
  820.   /**
  821.    * \sa sbIFeathersManager
  822.    */
  823.   getSkinDescription: function getSkinDescription(internalName) {
  824.     this._init();
  825.     return this._skins[internalName];
  826.   },
  827.   
  828.   
  829.   /**
  830.    * \sa sbIFeathersManager
  831.    */  
  832.   registerLayout: function registerLayout(layoutDesc) {
  833.     LayoutDescription.verify(layoutDesc);
  834.    
  835.     if (this._layouts[layoutDesc.url] == null) {
  836.       this._layoutCount++;
  837.     }
  838.     this._layouts[layoutDesc.url] = layoutDesc;
  839.     
  840.     // Notify observers
  841.     this._onUpdate();
  842.   },
  843.  
  844.   /**
  845.    * \sa sbIFeathersManager
  846.    */
  847.   unregisterLayout: function unregisterLayout(layoutDesc) {
  848.     if (this._layouts[layoutDesc.url]) {
  849.       delete this._layouts[layoutDesc.url];
  850.       this._layoutCount--;
  851.       
  852.       // Notify observers
  853.       this._onUpdate();  
  854.     }  
  855.   },
  856.     
  857.   /**
  858.    * \sa sbIFeathersManager
  859.    */    
  860.   getLayoutDescription: function getLayoutDescription(url) {
  861.     this._init();
  862.     return this._layouts[url];
  863.   }, 
  864.  
  865.   
  866.   /**
  867.    * \sa sbIFeathersManager
  868.    */
  869.   assertCompatibility: 
  870.   function assertCompatibility(layoutURL, internalName, aShowChrome, aOnTop) {
  871.     if (! (typeof(layoutURL) == "string" && typeof(internalName) == 'string')) {
  872.       throw Components.results.NS_ERROR_INVALID_ARG;
  873.     }
  874.     if (this._mappings[layoutURL] == null) {
  875.       this._mappings[layoutURL] = {};
  876.     }
  877.     this._mappings[layoutURL][internalName] = {showChrome: aShowChrome, onTop: aOnTop};
  878.     
  879.     // Notify observers
  880.     this._onUpdate();
  881.   },
  882.  
  883.   /**
  884.    * \sa sbIFeathersManager
  885.    */
  886.   unassertCompatibility: function unassertCompatibility(layoutURL, internalName) {
  887.     if (this._mappings[layoutURL]) {
  888.       delete this._mappings[layoutURL][internalName];
  889.       
  890.       // Notify observers
  891.       this._onUpdate();
  892.     }  
  893.   },
  894.   
  895.  
  896.   /**
  897.    * \sa sbIFeathersManager
  898.    */
  899.   isChromeEnabled: function isChromeEnabled(layoutURL, internalName) {
  900.     this._init();
  901.     
  902.     // TEMP fix for the Mac to enable the titlebar on the main window.
  903.     // See Bug 4363
  904.     var sysInfo = Components.classes["@mozilla.org/system-info;1"]
  905.                             .getService(Components.interfaces.nsIPropertyBag2);
  906.     var platform = sysInfo.getProperty("name");
  907.     
  908.     if (this._mappings[layoutURL]) {
  909.       if (this._mappings[layoutURL][internalName]) {
  910.         return this._mappings[layoutURL][internalName].showChrome == true;
  911.       }
  912.     }
  913.    
  914.     return false; 
  915.   },
  916.  
  917.   isOnTop: function isChromeEnabled(layoutURL, internalName) {
  918.     this._init();
  919.     
  920.     if (this._mappings[layoutURL]) {
  921.       if (this._mappings[layoutURL][internalName]) {
  922.         return this._mappings[layoutURL][internalName].onTop == true;
  923.       }
  924.     }
  925.    
  926.     return false; 
  927.   },
  928.  
  929.  
  930.   /**
  931.    * \sa sbIFeathersManager
  932.    */
  933.   getSkinsForLayout: function getSkinsForLayout(layoutURL) {
  934.     this._init();
  935.  
  936.     var skins = [];
  937.     
  938.     // Find skin descriptions that are compatible with the given layout.
  939.     if (this._mappings[layoutURL]) {
  940.       for (internalName in this._mappings[layoutURL]) {
  941.         var desc = this.getSkinDescription(internalName);
  942.         if (desc) {
  943.           skins.push(desc);
  944.         }
  945.       }
  946.     }   
  947.     return new ArrayEnumerator( skins );
  948.   },
  949.   
  950.   
  951.   /**
  952.    * \sa sbIFeathersManager
  953.    */
  954.   getLayoutsForSkin: function getLayoutsForSkin(internalName) {
  955.     this._init();
  956.  
  957.     var layouts = [];
  958.     
  959.     // Find skin descriptions that are compatible with the given layout.
  960.     for (var layout in this._mappings) {
  961.       if (internalName in this._mappings[layout]) {
  962.         var desc = this.getLayoutDescription(layout);
  963.         if (desc) {
  964.           layouts.push(desc);
  965.         }      
  966.       }
  967.     }
  968.       
  969.     return new ArrayEnumerator( layouts );
  970.   },
  971.  
  972.  
  973.   /**
  974.    * \sa sbIFeathersManager
  975.    */
  976.   switchFeathers: function switchFeathers(layoutURL, internalName) {
  977.     // don't allow this call if we're already switching
  978.     if (this._switching) {
  979.       return;
  980.     }
  981.  
  982.     this._init();
  983.  
  984.     layoutDescription = this.getLayoutDescription(layoutURL);
  985.     skinDescription = this.getSkinDescription(internalName);
  986.     
  987.     // Make sure we know about the requested skin and layout
  988.     if (layoutDescription == null || skinDescription == null) {
  989.       throw new Components.Exception("Unknown layout/skin passed to switchFeathers");
  990.     }
  991.     
  992.     // Check compatibility.
  993.     // True/false refer to the showChrome value, so check for undefined
  994.     // to determine compatibility.
  995.     if (this._mappings[layoutURL][internalName] === undefined) {
  996.       throw new Components.Exception("Skin [" + internalName + "] and Layout [" + layoutURL +
  997.             " are not compatible");
  998.     } 
  999.     
  1000.     // Notify that a select is about to occur
  1001.     this._onSelect(layoutDescription, skinDescription);
  1002.     
  1003.     // Remember the current feathers so that we can revert later if needed
  1004.     this._previousLayoutDataRemote.stringValue = this.currentLayoutURL;
  1005.     this._previousSkinDataRemote.stringValue = this.currentSkinName;
  1006.  
  1007.     // close the player window *before* changing the skin
  1008.     // otherwise Gecko tries to load an image that will go away right after and crashes
  1009.     // (songbird bug 3965)
  1010.     this._closePlayerWindow();
  1011.     
  1012.     var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  1013.     var callback = new FeathersManager_switchFeathers_callback(this, layoutURL, internalName);
  1014.     this._switching = true;
  1015.     timer.initWithCallback(callback, 0, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
  1016.   },
  1017.   
  1018.   
  1019.   /**
  1020.    * \sa sbIFeathersManager
  1021.    * Relaunch the main window
  1022.    */
  1023.   openPlayerWindow: function openPlayerWindow() {
  1024.     
  1025.     // First, check to make sure the current
  1026.     // feathers are valid
  1027.     var layoutDescription = this.getLayoutDescription(this.currentLayoutURL);
  1028.     var skinDescription = this.getSkinDescription(this.currentSkinName);
  1029.     if (layoutDescription == null || skinDescription == null) {
  1030.       // The current feathers are invalid. Switch to the defaults.
  1031.       this.switchFeathers(DEFAULT_MAIN_LAYOUT_URL, DEFAULT_SKIN_NAME);
  1032.       return;
  1033.     }
  1034.     
  1035.     
  1036.     var windowMediator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  1037.                                    .getService(Components.interfaces.nsIWindowMediator);
  1038.  
  1039.     // The core window (plugin host) is the only window which cannot be shut down
  1040.     var coreWindow = windowMediator.getMostRecentWindow(WINDOWTYPE_SONGBIRD_CORE);  
  1041.  
  1042.     // If no core window exists, then we are probably in test mode.
  1043.     // Therefore do nothing.
  1044.     if (coreWindow == null) {
  1045.       dump("FeathersManager.openPlayerWindow: unable to find window of type Songbird:Core. Test mode?\n");
  1046.       return;
  1047.     }
  1048.  
  1049.     var onTop;
  1050.     // On MacOS, popup=yes breaks too many things, so always set it to false
  1051.     var sysInfo = Components.classes["@mozilla.org/system-info;1"]
  1052.                             .getService(Components.interfaces.nsIPropertyBag2);
  1053.     var platform = sysInfo.getProperty("name");
  1054.     if (platform == "Darwin") {
  1055.       onTop = false;
  1056.     } else {
  1057.       onTop = this.isOnTop(this.currentLayoutURL, this.currentSkinName);
  1058.     }
  1059.  
  1060.     // if the native window manager service is available
  1061.     var nwm = Components.classes['@songbirdnest.com/integration/native-window-manager;1'];
  1062.     if (nwm) {
  1063.       nwm = nwm.getService(Components.interfaces.sbINativeWindowManager);
  1064.     }
  1065.  
  1066.     // Determine window features.  If chrome is enabled, make resizable.
  1067.     // Otherwise remove the titlebar.
  1068.     var chromeFeatures = "chrome,modal=no,resizable=yes,centerscreen,toolbar=yes";
  1069.     if ( (nwm && nwm.supportsOnTop) || !onTop ) {
  1070.       // if we have a native window manager component that supports onTop, 
  1071.       // or we don't want this window on top then set popup=no
  1072.       chromeFeatures += ",popup=no";
  1073.     } else {
  1074.       // if we don't have a native window manger component that supports onTop
  1075.       // and we want the window on top, set popup=yes
  1076.       chromeFeatures += ",popup=yes";
  1077.     }
  1078.     var showChrome = this.isChromeEnabled(this.currentLayoutURL, this.currentSkinName);
  1079.     if (showChrome) {
  1080.        chromeFeatures += ",titlebar=yes";
  1081.     } else {
  1082.        chromeFeatures += ",titlebar=no";
  1083.     }
  1084.     
  1085.     // Set the global chrome (window border and title) flag
  1086.     this._setChromeEnabled(showChrome);
  1087.     
  1088.     // Open the new player window
  1089.     var newMainWin = coreWindow.open(this.currentLayoutURL, "", chromeFeatures);
  1090.     newMainWin.focus();
  1091.  
  1092.     // if we can, tell the window manager to keep this window on top
  1093.     if (nwm && nwm.supportsOnTop) {
  1094.       nwm.setOnTop(newMainWin, onTop);
  1095.     }
  1096.   },
  1097.   
  1098.   
  1099.   /**
  1100.    * \sa sbIFeathersManager
  1101.    */  
  1102.   addListener: function addListener(listener) {
  1103.     if (! (listener instanceof Components.interfaces.sbIFeathersManagerListener))
  1104.     {
  1105.       throw Components.results.NS_ERROR_INVALID_ARG;
  1106.     }
  1107.     this._listeners.push(listener);
  1108.   },
  1109.   
  1110.   /**
  1111.    * \sa sbIFeathersManager
  1112.    */  
  1113.   removeListener: function removeListener(listener) {
  1114.     var index = this._listeners.indexOf(listener);
  1115.     if (index > -1) {
  1116.       this._listeners.splice(index,1);
  1117.     }
  1118.   },
  1119.  
  1120.  
  1121.   /**
  1122.    * Close all player windows (except the plugin host)
  1123.    */
  1124.   _closePlayerWindow: function _closePlayerWindow() {
  1125.     var windowMediator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  1126.                                    .getService(Components.interfaces.nsIWindowMediator);
  1127.  
  1128.     // The core window (plugin host) is the only window which cannot be shut down
  1129.     var coreWindow = windowMediator.getMostRecentWindow(WINDOWTYPE_SONGBIRD_CORE);  
  1130.     
  1131.     // If no core window exists, then we are probably in test mode.
  1132.     // Therefore do nothing.
  1133.     if (coreWindow == null) {
  1134.       dump("FeathersManager._closePlayerWindow: unable to find window of type Songbird:Core. Test mode?\n");
  1135.       return;
  1136.     }
  1137.  
  1138.     // Close all open windows other than the core, dominspector, and venkman.
  1139.     // This is needed in order to reset window chrome settings.
  1140.     var playerWindows = windowMediator.getEnumerator(null);
  1141.     while (playerWindows.hasMoreElements()) {
  1142.       var window = playerWindows.getNext();
  1143.       
  1144.       if (window != coreWindow) {
  1145.         
  1146.         // Don't close domi or other debug windows... that's just annoying
  1147.         var isDebugWindow = false;
  1148.         try {    
  1149.           var windowElement = window.document.documentElement;
  1150.           var windowID = windowElement.getAttribute("id");
  1151.           if (windowID == "JSConsoleWindow" || 
  1152.               windowID == "winInspectorMain" || 
  1153.               windowID == "venkman-window") 
  1154.           {
  1155.             isDebugWindow = true;
  1156.           }
  1157.         } catch (e) {}
  1158.         
  1159.         if (!isDebugWindow) {
  1160.  
  1161.           // Bug 5192
  1162.           // http://bugzilla.songbirdnest.com/show_bug.cgi?id=5192
  1163.           //
  1164.           // VERY lamely try to fix a mac crash bug by hacking away the menus
  1165.           // on any menubar on any window being closed by the feather-switch
  1166.           try {
  1167.             var menuArray = window.document.getElementsByTagName( "menubar" );
  1168.             for ( var j = 0; j < menuArray.length; j++ ) {
  1169.               var menu = menuArray[j];
  1170.               while ( menu.lastChild.previousSibling != null ) {
  1171.                 menu.removeChild( menu.lastChild );
  1172.               }
  1173.               if ( menu.lastChild ) {
  1174.                 menu.lastChild.setAttribute( "label", "" );
  1175.               }
  1176.             }
  1177.           } catch( e ) {
  1178.           }
  1179.         
  1180.           // Ask nicely.  The window should be able to cancel the onunload if
  1181.           // it chooses.
  1182.           window.close();
  1183.         }
  1184.       }
  1185.     }
  1186.   },
  1187.  
  1188.       
  1189.   /**
  1190.    * Indicates to the rest of the system whether or not to 
  1191.    * enable titlebars when opening windows
  1192.    */
  1193.   _setChromeEnabled: function _setChromeEnabled(enabled) {
  1194.  
  1195.     // Set the global chrome (window border and title) flag
  1196.     this._showChromeDataRemote.boolValue = enabled;
  1197.  
  1198.     var prefs = Components.classes["@mozilla.org/preferences-service;1"]
  1199.                           .getService(Components.interfaces.nsIPrefBranch);
  1200.  
  1201.     // Set the flags used to open the core window on startup.
  1202.     // Do a replacement in order to preserve whatever other features 
  1203.     // were specified.
  1204.     try {
  1205.       var titlebarRegEx = /(titlebar=)(no|yes)/;
  1206.       var replacement = (enabled) ? "$1yes" : "$1no";
  1207.       var defaultChromeFeatures = prefs.getCharPref("toolkit.defaultChromeFeatures");
  1208.       prefs.setCharPref("toolkit.defaultChromeFeatures",
  1209.               defaultChromeFeatures.replace(titlebarRegEx, replacement));
  1210.     } catch (e) {
  1211.       dump("\nFeathersManager._setChromeEnabled: Error setting defaultChromeFeatures pref! " +
  1212.             e.toString + "\n");
  1213.     }
  1214.   },      
  1215.       
  1216.  
  1217.   /**
  1218.    * Broadcasts an update event to all registered listeners
  1219.    */
  1220.   _onUpdate: function onUpdate() {
  1221.     this._listeners.forEach( function (listener) {
  1222.       listener.onFeathersUpdate();
  1223.     });
  1224.   },
  1225.  
  1226.  
  1227.   /**
  1228.    * Broadcasts an select (feathers switch) event to all registered listeners
  1229.    */
  1230.   _onSelect: function onSelect(layoutDesc, skinDesc) {
  1231.     // Verify args
  1232.     layoutDesc = layoutDesc.QueryInterface(Components.interfaces.sbILayoutDescription);
  1233.     skinDesc = skinDesc.QueryInterface(Components.interfaces.sbISkinDescription);
  1234.     
  1235.     // Broadcast notification
  1236.     this._listeners.forEach( function (listener) {
  1237.       listener.onFeathersSelectRequest(layoutDesc, skinDesc);
  1238.     });
  1239.   },
  1240.  
  1241.   _flushXULPrototypeCache: function flushXULPrototypeCache() {
  1242.     var prefs = Components.classes["@mozilla.org/preferences-service;1"]
  1243.                           .getService(Components.interfaces.nsIPrefBranch);
  1244.     var disabled = false;
  1245.     var userPref = false;
  1246.  
  1247.     try {
  1248.       disabled = prefs.getBoolPref("nglayout.debug.disable_xul_cache");
  1249.       userPref = true;
  1250.     }
  1251.     catch(e) {
  1252.     }
  1253.  
  1254.     if (!disabled) {
  1255.       prefs.setBoolPref("nglayout.debug.disable_xul_cache", true);
  1256.       prefs.setBoolPref("nglayout.debug.disable_xul_cache", false);
  1257.       if (!userPref) {
  1258.         prefs.clearUserPref("nglayout.debug.disable_xul_cache");
  1259.       }
  1260.     }
  1261.   },
  1262.  
  1263.   /**
  1264.    * Called by the observer service. Looks for XRE shutdown messages 
  1265.    */
  1266.   observe: function(subject, topic, data) {
  1267.     var os      = Components.classes["@mozilla.org/observer-service;1"]
  1268.                       .getService(Components.interfaces.nsIObserverService);
  1269.     switch (topic) {
  1270.     case "quit-application":
  1271.       os.removeObserver(this, "quit-application");
  1272.       this._deinit();
  1273.       break;
  1274.     }
  1275.   },
  1276.  
  1277.   /**
  1278.    * See nsISupports.idl
  1279.    */
  1280.   QueryInterface: function(iid) {
  1281.     if (!iid.equals(IID) &&
  1282.         !iid.equals(Components.interfaces.nsIObserver) && 
  1283.         !iid.equals(Components.interfaces.nsISupports))
  1284.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1285.     return this;
  1286.   }
  1287. }; // FeathersManager.prototype
  1288.  
  1289. /**
  1290.  * Callback helper for FeathersManager::switchFeathers
  1291.  * This is needed to make sure the window is really closed before we switch skins
  1292.  */
  1293. function FeathersManager_switchFeathers_callback(aFeathersManager,
  1294.                                                  aLayoutURL,
  1295.                                                  aInternalName) {
  1296.   this.feathersManager = aFeathersManager;
  1297.   this.layoutURL = aLayoutURL;
  1298.   this.internalName = aInternalName;
  1299. }
  1300.  
  1301. FeathersManager_switchFeathers_callback.prototype = {
  1302.   /**
  1303.    * \sa nsITimerCallback
  1304.    */
  1305.   notify: function FeathersManager_switchFeathers_callback_notify() {
  1306.     // Set new values
  1307.     this.feathersManager._layoutDataRemote.stringValue = this.layoutURL;
  1308.     this.feathersManager._skinDataRemote.stringValue = this.internalName;
  1309.  
  1310.     this.feathersManager._flushXULPrototypeCache();
  1311.     this.feathersManager.openPlayerWindow();
  1312.     this.feathersManager._switching = false;
  1313.     this.feathersManager = null;
  1314.   }
  1315. }; // FeathersManager_switchFeathers_callback.prototype
  1316.  
  1317.  
  1318.  
  1319.  
  1320.  
  1321. /**
  1322.  * ----------------------------------------------------------------------------
  1323.  * Registration for XPCOM
  1324.  * ----------------------------------------------------------------------------
  1325.  */
  1326. var gModule = {
  1327.   registerSelf: function(componentManager, fileSpec, location, type) {
  1328.     componentManager = componentManager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  1329.  
  1330.     componentManager.registerFactoryLocation(CID, CLASSNAME, CONTRACTID,
  1331.                                                fileSpec, location, type);
  1332.   },
  1333.  
  1334.   getClassObject: function(componentManager, cid, iid) {
  1335.     if (!iid.equals(Components.interfaces.nsIFactory))
  1336.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  1337.  
  1338.     if (cid.equals(CID)) {
  1339.        return { 
  1340.           createInstance: function(outer, iid) {
  1341.             if (outer != null)
  1342.               throw Components.results.NS_ERROR_NO_AGGREGATION;
  1343.               
  1344.             // Make the feathers manager  
  1345.             return (new FeathersManager()).QueryInterface(iid);;
  1346.           }
  1347.        };
  1348.     }
  1349.     
  1350.     throw Components.results.NS_ERROR_NO_INTERFACE;
  1351.   },
  1352.  
  1353.   canUnload: function(componentManager) { 
  1354.     return true; 
  1355.   }
  1356. }; // gModule
  1357.  
  1358. function NSGetModule(comMgr, fileSpec) {
  1359.   return gModule;
  1360. } // NSGetModule
  1361.  
  1362.  
  1363.  
  1364.