home *** CD-ROM | disk | FTP | other *** search
/ Freelog 100 / FreelogNo100-NovembreDecembre2010.iso / Multimedia / Songbird / Songbird_1.8.0-1800_windows-i686-msvc8.exe / components / sbFeathersManager.js < prev    next >
Text File  |  2010-08-30  |  45KB  |  1,480 lines

  1. /*
  2.  *=BEGIN SONGBIRD GPL
  3.  *
  4.  * This file is part of the Songbird web player.
  5.  *
  6.  * Copyright(c) 2005-2010 POTI, Inc.
  7.  * http://www.songbirdnest.com
  8.  *
  9.  * This file may be licensed under the terms of of the
  10.  * GNU General Public License Version 2 (the ``GPL'').
  11.  *
  12.  * Software distributed under the License is distributed
  13.  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
  14.  * express or implied. See the GPL for the specific language
  15.  * governing rights and limitations.
  16.  *
  17.  * You should have received a copy of the GPL along with this
  18.  * program. If not, go to http://www.gnu.org/licenses/gpl.html
  19.  * or write to the Free Software Foundation, Inc.,
  20.  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21.  *
  22.  *=END SONGBIRD GPL
  23.  */
  24.  
  25. /**
  26.  * \file sbFeathersManager.js
  27.  * \brief Coordinates the loading of feathers (combination of skin and XUL window layout)
  28.  */ 
  29.  
  30.  
  31. //
  32. // TODO:
  33. //  * Explore skin/layout versioning issues?
  34. // 
  35.  
  36. Components.utils.import("resource://app/jsmodules/ObserverUtils.jsm");
  37. Components.utils.import("resource://app/jsmodules/StringUtils.jsm");
  38. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  39.  
  40. const Ci = Components.interfaces;
  41. const Cc = Components.classes; 
  42. const Cr = Components.results;
  43. const Cu = Components.utils;
  44.  
  45. const CONTRACTID = "@songbirdnest.com/songbird/feathersmanager;1";
  46. const CLASSNAME = "Songbird Feathers Manager Service Interface";
  47. const CID = Components.ID("{99f24350-a67f-11db-befa-0800200c9a66}");
  48. const IID = Ci.sbIFeathersManager;
  49.  
  50.  
  51. const RDFURI_ADDON_ROOT               = "urn:songbird:addon:root" 
  52. const PREFIX_NS_SONGBIRD              = "http://www.songbirdnest.com/2007/addon-metadata-rdf#";
  53.  
  54. const CHROME_PREFIX = "chrome://"
  55.  
  56. // This DataRemote is required to indicate to the feather manager
  57. // that it is currently running in test mode. 
  58. const DATAREMOTE_TESTMODE = "__testmode__";
  59.  
  60. //
  61. // Default feather/layout/skin preference prefs.
  62. //
  63. // * PREF_DEFAULT_MAIN_LAYOUT
  64. //     - Default main layout to start with. This is typically the "main player"
  65. //       layout. This layout will be used when the player window is opened for
  66. //       the first time.
  67. //
  68. // * PREF_DEFAULT_SECONDARY_LAYOUT
  69. //     - The default secondary layout. This layout is typically an alternative 
  70. //       view to the main layout defined in |PREF_DEFAULT_MAIN_LAYOUT| - such
  71. //       as a "mini-player" view.
  72. //     - NOTE: This is an optional pref.
  73. //
  74. // * PREF_DEFAULT_SKIN_LOCALNAME
  75. //     - The local name of the default feather. This is defined in the install.rdf
  76. //       of the shipped feather, in the |<songbird:internalName/>| tag.
  77. //
  78. // * PREF_DEFAULT_FEATHER_ID
  79. //     - The ID of the default feathers extension. This is defined in the
  80. //       install.rdf of the shipped feather, in the <em:id/> tag.
  81. //
  82. //
  83. // NOTE: Changes to the default layout/skin/feather will be automatically picked up
  84. //       in the feathers unit test.
  85. //
  86. const PREF_DEFAULT_MAIN_LAYOUT       = "songbird.feathers.default_main_layout";
  87. const PREF_DEFAULT_SECONDARY_LAYOUT  = "songbird.feathers.default_secondary_layout";
  88. const PREF_DEFAULT_SKIN_INTERNALNAME = "songbird.feathers.default_skin_internalname";
  89. const PREF_DEFAULT_FEATHER_ID        = "songbird.feathers.default_feather_id";
  90.  
  91. // Pref to ensure sanity tests are run the first time the feathers manager
  92. // has started on a profile.
  93. const PREF_FEATHERS_MANAGER_HAS_STARTED  = "songbird.feathersmanager.hasStarted";
  94.  
  95. const WINDOWTYPE_SONGBIRD_PLAYER      = "Songbird:Main";
  96. const WINDOWTYPE_SONGBIRD_CORE        = "Songbird:Core";
  97.  
  98. Cu.import("resource://app/jsmodules/RDFHelper.jsm");
  99. Cu.import("resource://app/jsmodules/SBDataRemoteUtils.jsm");
  100.  
  101. /**
  102.  * /class ArrayEnumerator
  103.  * /brief Converts a js array into an nsISimpleEnumerator
  104.  */
  105. function ArrayEnumerator(array)
  106. {
  107.   this.data = array;
  108. }
  109. ArrayEnumerator.prototype = {
  110.  
  111.   index: 0,
  112.  
  113.   getNext: function() {
  114.     return this.data[this.index++];
  115.   },
  116.  
  117.   hasMoreElements: function() {
  118.     if (this.index < this.data.length)
  119.       return true;
  120.     else
  121.       return false;
  122.   },
  123.  
  124.   QueryInterface: function(iid)
  125.   {
  126.     if (!iid.equals(Ci.nsISimpleEnumerator) &&
  127.         !iid.equals(Ci.nsISupports))
  128.       throw Components.results.NS_ERROR_NO_INTERFACE;
  129.     return this;
  130.   }
  131. }
  132.  
  133. /**
  134.  * sbISkinDescription
  135.  */
  136. function SkinDescription() {};
  137. SkinDescription.prototype = {
  138.   // TODO Expand?
  139.   requiredProperties: [ "internalName" ],
  140.   optionalProperties: [ "name" ],
  141.   QueryInterface: function(iid) {
  142.     if (!iid.equals(Ci.sbISkinDescription))
  143.       throw Components.results.NS_ERROR_NO_INTERFACE;
  144.     return this;
  145.   }
  146. };
  147.  
  148. /**
  149.  * sbILayoutDescription
  150.  */
  151. function LayoutDescription() {};
  152. LayoutDescription.prototype = {
  153.   // TODO Expand?
  154.   requiredProperties: [ "name", "url" ],
  155.   optionalProperties: [ ],
  156.   QueryInterface: function(iid) {
  157.     if (!iid.equals(Ci.sbILayoutDescription))
  158.       throw Components.results.NS_ERROR_NO_INTERFACE;
  159.     return this;
  160.   }
  161. };
  162.  
  163. /**
  164.  * Static function that verifies the contents of the given description
  165.  *
  166.  * Example:
  167.  *   try {
  168.  *     LayoutDescription.verify(layout);
  169.  *   } catch (e) {
  170.  *     reportError(e);
  171.  *   }
  172.  *
  173.  */
  174. LayoutDescription.verify = SkinDescription.verify = function( description ) 
  175. {
  176.   for (var i = 0; i < this.prototype.requiredProperties.length; i++) {
  177.     var property = this.prototype.requiredProperties[i];
  178.     if (! (typeof(description[property]) == 'string'
  179.              && description[property].length > 0)) 
  180.     {
  181.       throw("Invalid description. '" + property + "' is a required property.");
  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.  
  194. AddonMetadataReader.prototype = {
  195.   _manager: null,
  196.  
  197.   /**
  198.    * Populate FeathersManager using addon metadata
  199.    */
  200.   loadMetadata: function(manager) {
  201.     //debug("AddonMetadataReader: loadMetadata\n");
  202.     this._manager = manager;
  203.     
  204.     var addons = RDFHelper.help(
  205.       "rdf:addon-metadata",
  206.       "urn:songbird:addon:root",
  207.       RDFHelper.DEFAULT_RDF_NAMESPACES
  208.     );
  209.     
  210.     for (var i = 0; i < addons.length; i++) {
  211.       // first a little workaround to for backwards compatibility 
  212.       // with the now obsolete <feathers> element
  213.       // TODO: remove this when we stop supporting 0.4 feathers
  214.       var feathersHub = addons[i];
  215.       if (feathersHub.feathers) {
  216.         Components.utils.reportError("Feathers Metadata Reader: The <feathers/> element in " +
  217.                        "install.rdf is deprecated and will go away in a future version.");
  218.         feathersHub = feathersHub.feathers[0];
  219.       }
  220.       
  221.       if (feathersHub.skin) {
  222.         var skins = feathersHub.skin;
  223.         for (var j = 0; j < skins.length; j++) {
  224.           try {
  225.             this._registerSkin(addons[i], skins[j]);
  226.           }
  227.           catch (e) {
  228.             this._reportErrors("", [  "An error occurred while processing " +
  229.                   "extension " + addons[i].Value + ".  Exception: " + e  ]);
  230.           }
  231.         }
  232.       }
  233.       
  234.       if (feathersHub.layout) {
  235.         var layouts = feathersHub.layout;
  236.         for (var j = 0; j < layouts.length; j++) {
  237.           try {
  238.             this._registerLayout(addons[i], layouts[j]);
  239.           }
  240.           catch (e) {
  241.             this._reportErrors("", [  "An error occurred while processing " +
  242.                   "extension " + addons[i].Value + ".  Exception: " + e  ]);
  243.           }
  244.         }
  245.       }
  246.     }
  247.   },
  248.   
  249.   
  250.   /**
  251.    * Extract skin metadata
  252.    */
  253.   _registerSkin: function _registerSkin(addon, skin) {
  254.     var description = new SkinDescription();
  255.     
  256.     // Array of error messages
  257.     var errorList = [];
  258.     
  259.     // NB: there is a "verify" function as well, 
  260.     //     but here we build and verify at the same time
  261.     for each (var prop in SkinDescription.prototype.requiredProperties) {
  262.       if(skin[prop][0]) {
  263.         description[prop] = skin[prop][0];
  264.       }
  265.       else {
  266.         errorList.push("Missing required <"+prop+"> element.");
  267.       }
  268.     }
  269.  
  270.     for each (var prop in SkinDescription.prototype.optionalProperties) {
  271.       if(skin[prop] && skin[prop][0]) {
  272.         description[prop] = skin[prop][0];
  273.       }
  274.     }
  275.     
  276.     // If errors were encountered, then do not submit 
  277.     // to the Feathers Manager
  278.     if (errorList.length > 0) {
  279.       this._reportErrors(
  280.           "Ignoring skin addon in the install.rdf of extension " +
  281.           addon.Value + ". Message: ", errorList);
  282.       return;
  283.     }
  284.  
  285.     // Don't register skins that have no name, but throw up a friendly
  286.     // informative message so users don't see gross red error messages
  287.     // and misinterpret them.
  288.     if (!skin["name"]) {
  289.       var consoleSvc = Cc["@mozilla.org/consoleservice;1"]
  290.                          .getService(Ci.nsIConsoleService);
  291.       consoleSvc.logStringMessage(
  292.           "Feathers Metadata Reader: Skipping registration of Feather with " +
  293.           "skin internal name: '" + skin["internalName"] + "' due to " +
  294.           "undefined or blank skin name.");
  295.       return;
  296.     }
  297.     
  298.     // Submit description
  299.     this._manager.registerSkin(description);
  300.     //debug("AddonMetadataReader: registered skin " + description.internalName
  301.     //        + " from addon " + addon.Value + " \n");
  302.  
  303.     if (skin.compatibleLayout) {
  304.       var compatibleLayouts = skin.compatibleLayout;
  305.       var hasRegisteredDefault = false;
  306.       for (var i = 0; i < compatibleLayouts.length; i++) {
  307.         var compatibleLayout = compatibleLayouts[i];
  308.  
  309.         var layoutUrl;
  310.         // TODO: FIXME! this is inconsistent with other capitalizations!!
  311.         if(compatibleLayout.layoutURL && compatibleLayout.layoutURL[0].length != 0) {
  312.           layoutUrl  = compatibleLayout.layoutURL[0];
  313.         }
  314.         else {
  315.           errorList.push("layoutUrl was missing or incorrect.");
  316.           continue;
  317.         }
  318.   
  319.         var showChrome = false;
  320.         if (compatibleLayout.showChrome && 
  321.             compatibleLayout.showChrome[0] == "true") {
  322.           showChrome = true;
  323.         }
  324.         var onTop = false
  325.         if (compatibleLayout.onTop && 
  326.             compatibleLayout.onTop[0] == "true") {
  327.           onTop = true;
  328.         }
  329.   
  330.         this._manager.assertCompatibility(
  331.           layoutUrl, 
  332.           description.internalName, 
  333.           showChrome, 
  334.           onTop
  335.         );
  336.  
  337.         // If this is the first element in the RDF - or the layout is set to
  338.         // be the default layout, let's assign these attributes here.
  339.         if ((i == 0) || 
  340.             (compatibleLayout.isDefault && 
  341.             compatibleLayout.isDefault[0] == "true")) 
  342.         {
  343.           this._manager.setDefaultLayout(layoutUrl, description.internalName);
  344.           
  345.           if (hasRegisteredDefault) {
  346.             Components.utils.reportError(
  347.               "A default layout has already been assigned for " + 
  348.               description.internalName
  349.             );
  350.           }
  351.           
  352.           hasRegisteredDefault = true;
  353.         }
  354.       }
  355.       
  356.       if (errorList.length > 0) {
  357.         this._reportErrors(
  358.             "Ignoring a <compatibleLayout> in the install.rdf of extension " +
  359.             addon.Value + ". Message: ", errorList);
  360.         return;
  361.       }
  362.     }
  363.   },
  364.  
  365.   /**
  366.    * Extract layout metadata
  367.    */
  368.   _registerLayout: function _processLayout(addon, layout) {
  369.     var description = new LayoutDescription();
  370.  
  371.     // Array of error messages
  372.     var errorList = [];
  373.     
  374.     // NB: there is a "verify" function as well, 
  375.     //     but here we build and verify at the same time
  376.     for each (var prop in LayoutDescription.prototype.requiredProperties) {
  377.       if(layout[prop][0]) {
  378.         description[prop] = layout[prop][0];
  379.       }
  380.       else {
  381.         errorList.push("Missing required <"+prop+"> element.");
  382.       }
  383.     }
  384.  
  385.     for each (var prop in LayoutDescription.prototype.optionalProperties) {
  386.       if(layout[prop][0]) {
  387.         description[prop] = layout[prop][0];
  388.       }
  389.     }
  390.  
  391.     // If errors were encountered, then do not submit 
  392.     // to the Feathers Manager
  393.     if (errorList.length > 0) {
  394.       this._reportErrors(
  395.           "Ignoring layout addon in the install.rdf of extension " +
  396.           addon.Value + ". Message: ", errorList);
  397.       return;
  398.     }
  399.  
  400.     // Resolve any localised layout names to their actual strings
  401.     if (description.name.substr(0,CHROME_PREFIX.length) == CHROME_PREFIX)
  402.     {
  403.       var name = SBString("feathers.name.unnamed");
  404.       var split = description.name.split("#", 2);
  405.       if (split.length == 2) {
  406.         var bundle = new SBStringBundle(split[0]);
  407.         name = bundle.get(split[1], name);
  408.       }
  409.       description.name = name;
  410.     }
  411.     // Submit description
  412.     this._manager.registerLayout(description);
  413.     //debug("AddonMetadataReader: registered layout " + description.name +
  414.     //     " from addon " + addon.Value + "\n");    
  415.  
  416.     // TODO: should we error out here if there are errors already?
  417.     if (layout.compatibleSkin) {
  418.       var compatibleSkins = layout.compatibleSkin;
  419.       for (var i = 0; i < compatibleSkins.length; i++) {
  420.         var compatibleSkin = compatibleSkins[i];
  421.   
  422.         var internalName;
  423.         if (compatibleSkin.internalName && 
  424.            compatibleSkin.internalName[0].length != 0) 
  425.         {
  426.           internalName  = compatibleSkin.internalName[0];
  427.         }
  428.         else {
  429.           errorList.push("internalName was missing or incorrect.");
  430.           continue;
  431.         }
  432.   
  433.         var showChrome = false;
  434.         if (compatibleSkin.showChrome && 
  435.             compatibleSkin.showChrome[0] == "true") {
  436.           showChrome = true;
  437.         }
  438.         var onTop = false;
  439.         if (compatibleSkin.onTop && 
  440.             compatibleSkin.onTop[0] == "true") {
  441.           onTop = true;
  442.         }
  443.   
  444.         this._manager.assertCompatibility(
  445.           description.url, 
  446.           internalName, 
  447.           showChrome, 
  448.           onTop
  449.         );
  450.       }
  451.     }
  452.  
  453.     // Report errors
  454.     if (errorList.length > 0) {
  455.       this._reportErrors(
  456.           "Error finding compatibility information for layout " +
  457.           description.name + " in the install.rdf " +
  458.           "of extension " + addon.Value + ". Message: ", errorList);
  459.     }
  460.   },
  461.   
  462.   /**
  463.    * \brief Dump a list of errors to the console and jsconsole
  464.    *
  465.    * \param contextMessage Additional prefix to use before every line
  466.    * \param errorList Array of error messages
  467.    */
  468.   _reportErrors: function _reportErrors(contextMessage, errorList) {
  469.     for (var i = 0; i  < errorList.length; i++) {
  470.       Components.utils.reportError("Feathers Metadata Reader: " 
  471.                                        + contextMessage + errorList[i]);
  472.     }
  473.   }
  474. }
  475.  
  476.  
  477.  
  478.  
  479.  
  480.  
  481.  
  482.  
  483.  
  484.  
  485.  
  486. /**
  487.  * /class FeathersManager
  488.  * /brief Coordinates the loading of feathers
  489.  *
  490.  * Acts as a registry for skins and layout (known as feathers)
  491.  * and manages compatibility and selection.
  492.  *
  493.  * \sa sbIFeathersManager
  494.  */
  495. function FeathersManager() {
  496.  
  497.   this._observerSet = new ObserverSet();
  498.  
  499.   // We need to init at final UI startup after the Extension Manager has checked
  500.   // for feathers (required for NO_EM_RESTART support).
  501.   this._observerSet.add(this, "final-ui-startup", false, true);
  502.  
  503.   // We need to unhook things on shutdown
  504.   this._observerSet.add(this, "quit-application", false, true);
  505.   
  506.   this._skins = {};
  507.   this._layouts = {};
  508.   this._skinDefaults = {};
  509.   this._mappings = {};
  510.   this._listeners = [];
  511. };
  512. FeathersManager.prototype = {
  513.   classDescription:  CLASSNAME,
  514.   classID:           CID,
  515.   contractID:        CONTRACTID,
  516.   _xpcom_categories:
  517.   [{
  518.     category: "app-startup",
  519.     entry: "feathers-manager",
  520.     value: "service," + CONTRACTID
  521.   }],
  522.   constructor: FeathersManager,
  523.   
  524.   _observerSet: null,
  525.  
  526.   _layoutDataRemote: null,
  527.   _skinDataRemote: null,
  528.  
  529.   _previousLayoutDataRemote: null,
  530.   _previousSkinDataRemote: null,
  531.   
  532.   _showChromeDataRemote: null,
  533.  
  534.   _switching: false,
  535.  
  536.   // Ignore autoswitching when the feathers manager is run for the first time.
  537.   _ignoreAutoswitch: false,
  538.  
  539.   // feather, layout, and skin name defaults
  540.   _defaultLayoutURL: "",
  541.   _defaultSecondaryLayoutURL: "",
  542.   _defaultSkinName: "",
  543.   
  544.   // Hash of skin descriptions keyed by internalName (e.g. classic/1.0)
  545.   _skins: null,
  546.   
  547.   // Hash of layout descriptions keyed by URL
  548.   _layouts: null,
  549.   
  550.   // Hash of default layouts for skins.
  551.   _skinDefaults: null,
  552.                                                                   
  553.   
  554.   
  555.   // Hash of layout URL to hash of compatible skin internalNames, pointing to 
  556.   // {showChrome,onTop} objects.  
  557.   //
  558.   // eg
  559.   // {  
  560.   //     mainwin.xul: {
  561.   //       blueskin: {showChrome:true, onTop:false},
  562.   //       redskin: {showChrome:false, onTop:true},
  563.   //     }
  564.   // }
  565.   //
  566.   // Compatibility is determined by whether or not a internalName
  567.   // key is *defined* in the hash, not the actual value it points to.
  568.   _mappings: null,
  569.   
  570.   
  571.   // Array of sbIFeathersChangeListeners
  572.   _listeners: null,
  573.  
  574.   _layoutCount: 0,
  575.   _skinCount: 0,
  576.  
  577.   // nsIURI, our agent sheet
  578.   _agentSheetURI: null,
  579.   
  580.  
  581.   /**
  582.    * Initializes dataremotes and triggers the AddonMetadataReader
  583.    * to explore installed extensions and register any feathers.
  584.    *
  585.    * Note that this function is not run until a get method is called
  586.    * on the feathers manager.  This is to defer loading the metadata
  587.    * as long as possible and avoid impacting startup time.
  588.    * 
  589.    */
  590.   _init: function init() {
  591.     // before reading the prefs, make sure they're stabilized; see bug
  592.     // 14504 for details.
  593.     Cc["@mozilla.org/browser/browserglue;1"]
  594.       .getService(Ci.nsIObserver)
  595.       .observe(null, "prefservice:after-app-defaults", null);
  596.     
  597.     var AppPrefs = Cc["@mozilla.org/fuel/application;1"]
  598.                      .getService(Ci.fuelIApplication).prefs;
  599.  
  600.     // If the safe-mode dialog was requested to disable all addons, our
  601.     // basic layouts and default skin have been disabled too. We need to 
  602.     // check if that's the case, and reenable them if needed
  603.     var defaultFeather = AppPrefs.getValue(PREF_DEFAULT_FEATHER_ID, "");
  604.     this._ensureAddOnEnabled(defaultFeather);
  605.  
  606.     // Read in defaults
  607.     this._defaultLayoutURL = AppPrefs.getValue(PREF_DEFAULT_MAIN_LAYOUT, "");
  608.     this._defaultSecondaryLayoutURL = 
  609.       AppPrefs.getValue(PREF_DEFAULT_SECONDARY_LAYOUT, "");
  610.     this._defaultSkinName = AppPrefs.getValue(PREF_DEFAULT_SKIN_INTERNALNAME, "");
  611.     
  612.     if (!AppPrefs.has(PREF_FEATHERS_MANAGER_HAS_STARTED)) {
  613.       // Ignore the autoswitch detecting for the first run, this prevents
  614.       // jumping between two shipped feathers.
  615.       this._ignoreAutoswitch = true;
  616.  
  617.       // Now the feather manager has started at least once.
  618.       AppPrefs.setValue(PREF_FEATHERS_MANAGER_HAS_STARTED, true);
  619.     }
  620.  
  621.  
  622.     // Register our agent sheet for form styling
  623.     this._agentSheetURI = Cc["@mozilla.org/network/io-service;1"]
  624.                             .getService(Ci.nsIIOService)
  625.                             .newURI("chrome://songbird/skin/formsImport.css",
  626.                                     null, null);
  627.     var styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
  628.                               .getService(Ci.nsIStyleSheetService);
  629.     styleSheetService.loadAndRegisterSheet(this._agentSheetURI,
  630.                                            styleSheetService.AGENT_SHEET);
  631.  
  632.     // Make dataremotes to persist feathers settings
  633.     var createDataRemote =  new Components.Constructor(
  634.                   "@songbirdnest.com/Songbird/DataRemote;1",
  635.                   Ci.sbIDataRemote, "init");
  636.  
  637.     this._layoutDataRemote = createDataRemote("feathers.selectedLayout", null);
  638.     this._skinDataRemote = createDataRemote("selectedSkin", "general.skins.");
  639.     
  640.     this._previousLayoutDataRemote = createDataRemote("feathers.previousLayout", null);
  641.     this._previousSkinDataRemote = createDataRemote("feathers.previousSkin", null);
  642.  
  643.     // Check to make sure we have a skin set; if not, then set the current
  644.     // skin to be the default skin.  If we don't have a default skin, then
  645.     // we'll really fubar'd.  (bug 20528)
  646.     if (!this.currentSkinName) {
  647.       this._skinDataRemote.stringValue = this._defaultSkinName;
  648.     }
  649.     // TODO: Rename accessibility.enabled?
  650.     this._showChromeDataRemote = createDataRemote("accessibility.enabled", null);
  651.     
  652.     // Load the feathers metadata
  653.     var metadataReader = new AddonMetadataReader();
  654.     metadataReader.loadMetadata(this);
  655.     
  656.     // If no layout url has been specified, set to default
  657.     if (this._layoutDataRemote.stringValue == "") {
  658.       this._layoutDataRemote.stringValue = this._defaultLayoutURL;
  659.     }
  660.  
  661.     // Ensure chrome enabled is set appropriately
  662.     var showChrome = this.isChromeEnabled(this.currentLayoutURL,
  663.                                           this.currentSkinName);
  664.     this._setChromeEnabled(showChrome);
  665.   },
  666.   
  667.   /**
  668.    * Called on xpcom-shutdown
  669.    */
  670.   _deinit: function deinit() {
  671.     this._observerSet.removeAll();
  672.     this._observerSet = null;
  673.     this._skins = null;
  674.     this._layouts = null;
  675.     this._mappings = null;
  676.     this._listeners = null;
  677.     this._layoutDataRemote = null;
  678.     this._skinDataRemote = null;
  679.     this._previousLayoutDataRemote = null;
  680.     this._previousSkinDataRemote = null;
  681.     this._showChromeDataRemote = null;
  682.   },
  683.     
  684.   /**
  685.    * \sa sbIFeathersManager
  686.    */
  687.   get currentSkinName() {
  688.     return this._skinDataRemote.stringValue;
  689.   },
  690.   
  691.   
  692.   /**
  693.    * \sa sbIFeathersManager
  694.    */  
  695.   get currentLayoutURL() {
  696.     return this._layoutDataRemote.stringValue;
  697.   },
  698.   
  699.   
  700.   /**
  701.    * \sa sbIFeathersManager
  702.    */  
  703.   get previousSkinName() {
  704.     // Test to make sure the previous skin exists
  705.     var skin = this.getSkinDescription(this._previousSkinDataRemote.stringValue);
  706.     
  707.     // If the skin exists, then return the skin name
  708.     if (skin) {
  709.       return skin.internalName;
  710.     }
  711.     
  712.     // Otherwise, return the default skin
  713.     return this._defaultSkinName;
  714.   },
  715.   
  716.   
  717.   /**
  718.    * \sa sbIFeathersManager
  719.    */  
  720.   get previousLayoutURL() {
  721.     // Test to make sure the previous layout exists
  722.     var layout = this.getLayoutDescription(this._previousLayoutDataRemote.stringValue);
  723.     
  724.     // If the layout exists, then return the url/identifier
  725.     if (layout) {
  726.       return layout.url;
  727.     }
  728.     
  729.     // Otherwise, return the default 
  730.     
  731.     // Use the main default unless it is currently
  732.     // active. This way if the user reverts for the
  733.     // first time they will end up in the miniplayer.
  734.     var layoutURL = this._defaultLayoutURL;
  735.     if (this.currentLayoutURL == layoutURL) {
  736.       layoutURL = this._defaultSecondaryLayoutURL;
  737.     }
  738.     
  739.     return layoutURL;    
  740.   },
  741.  
  742.  
  743.   /**
  744.    * \sa sbIFeathersManager
  745.    */ 
  746.   get skinCount() {
  747.     return this._skinCount;
  748.   },
  749.   
  750.   /**
  751.    * \sa sbIFeathersManager
  752.    */  
  753.   get layoutCount() {
  754.     return this._layoutCount;
  755.   },
  756.  
  757.  
  758.   /**
  759.    * \sa sbIFeathersManager
  760.    */
  761.   getSkinDescriptions: function getSkinDescriptions() {
  762.     // Copy all the descriptions into an array, and then return an enumerator
  763.     return new ArrayEnumerator( [this._skins[key] for (key in this._skins)] );
  764.   },
  765.  
  766.   /**
  767.    * \sa sbIFeathersManager
  768.    */
  769.   getLayoutDescriptions: function getLayoutDescriptions() {
  770.     // Copy all the descriptions into an array, and then return an enumerator
  771.     return new ArrayEnumerator( [this._layouts[key] for (key in this._layouts)] );
  772.   },
  773.   
  774.   
  775.   /**
  776.    * \sa sbIFeathersManager
  777.    */  
  778.   registerSkin: function registerSkin(skinDesc) {
  779.   
  780.     SkinDescription.verify(skinDesc);
  781.     
  782.     if (this._skins[skinDesc.internalName] == null) {
  783.       this._skinCount++;
  784.     }
  785.     this._skins[skinDesc.internalName] = skinDesc;
  786.     
  787.     // Notify observers
  788.     this._onUpdate();
  789.   },
  790.  
  791.   /**
  792.    * \sa sbIFeathersManager
  793.    */
  794.   unregisterSkin: function unregisterSkin(skinDesc) {
  795.     if (this._skins[skinDesc.internalName]) {
  796.       delete this._skins[skinDesc.internalName];
  797.       this._skinCount--;
  798.       
  799.       // Notify observers
  800.       this._onUpdate();
  801.     }
  802.   },
  803.  
  804.   /**
  805.    * \sa sbIFeathersManager
  806.    */
  807.   getSkinDescription: function getSkinDescription(internalName) {
  808.     return this._skins[internalName];
  809.   },
  810.   
  811.   
  812.   /**
  813.    * \sa sbIFeathersManager
  814.    */  
  815.   registerLayout: function registerLayout(layoutDesc) {
  816.     LayoutDescription.verify(layoutDesc);
  817.  
  818.     if (!(layoutDesc.url in this._layouts)) {
  819.       this._layoutCount++;
  820.     }
  821.     this._layouts[layoutDesc.url] = layoutDesc;
  822.     
  823.     // Notify observers
  824.     this._onUpdate();
  825.   },
  826.  
  827.   /**
  828.    * \sa sbIFeathersManager
  829.    */
  830.   unregisterLayout: function unregisterLayout(layoutDesc) {
  831.     if (layoutDesc.url in this._layouts) {
  832.       delete this._layouts[layoutDesc.url];
  833.       this._layoutCount--;
  834.       
  835.       // Notify observers
  836.       this._onUpdate();  
  837.     }  
  838.   },
  839.     
  840.   /**
  841.    * \sa sbIFeathersManager
  842.    */    
  843.   getLayoutDescription: function getLayoutDescription(url) {
  844.     return (url in this._layouts ? this._layouts[url] : null);
  845.   }, 
  846.  
  847.   
  848.   /**
  849.    * \sa sbIFeathersManager
  850.    */
  851.   assertCompatibility: 
  852.   function assertCompatibility(layoutURL, internalName, aShowChrome, aOnTop) {
  853.     if (! (typeof(layoutURL) == "string" && typeof(internalName) == 'string')) {
  854.       throw Components.results.NS_ERROR_INVALID_ARG;
  855.     }
  856.     if (this._mappings[layoutURL] == null) {
  857.       this._mappings[layoutURL] = {};
  858.     }
  859.     this._mappings[layoutURL][internalName] = {showChrome: aShowChrome, onTop: aOnTop};
  860.  
  861.     // check if this layout/skin combination has already been seen, 
  862.     // if it hasn't then we want to switch to it in openPlayerWindow,
  863.     // so remember it
  864.     var branch = this.getFeatherPrefBranch(layoutURL, internalName);
  865.     var seen = false;
  866.     try {
  867.       seen = branch.getBoolPref("seen");
  868.     } catch (e) { }
  869.     if (!seen) {
  870.       branch.setBoolPref("seen", true);
  871.       if (!this._autoswitch && !this._ignoreAutoswitch) {
  872.         this._autoswitch = {};
  873.         this._autoswitch.skin = internalName;
  874.         this._autoswitch.layoutURL = layoutURL;
  875.       }
  876.     }
  877.     
  878.     // Notify observers
  879.     this._onUpdate();
  880.   },
  881.  
  882.   /**
  883.    * \sa sbIFeathersManager
  884.    */
  885.   unassertCompatibility: function unassertCompatibility(layoutURL, internalName) {
  886.     if (this._mappings[layoutURL]) {
  887.       delete this._mappings[layoutURL][internalName];
  888.       
  889.       // Notify observers
  890.       this._onUpdate();
  891.     }  
  892.   },
  893.   
  894.   /**
  895.    * \sa sbIFeathersManager
  896.    */
  897.   setDefaultLayout: function setDefaultLayout(aLayoutURL, aInternalName) {
  898.     if (!(typeof(aLayoutURL) == "string" && 
  899.           typeof(aInternalName) == "string")) 
  900.     {
  901.       throw Components.results.NS_ERROR_INVALID_ARG;
  902.     }
  903.     
  904.     this._skinDefaults[aInternalName] = aLayoutURL;
  905.     this._onUpdate();  // notify observers
  906.   },
  907.   
  908.   /**
  909.    * \sa sbIFeathersManager
  910.    */
  911.   getDefaultLayout: function getDefaultLayout(aInternalName) {
  912.     if (!typeof(aInternalName) == "string") {
  913.       throw Components.results.NS_ERROR_INVALID_ARG;
  914.     }
  915.     
  916.     var defaultLayoutURL = this._skinDefaults[aInternalName];
  917.     
  918.     // If a default URL isn't registered, just use the first compatible 
  919.     // layout registered for the skin identifier.
  920.     if (!defaultLayoutURL) {
  921.       for (var curLayoutURL in this._mappings) {
  922.         if (aInternalName in this._mappings[curLayoutURL]) {
  923.           defaultLayoutURL = curLayoutURL;
  924.           break;
  925.         }
  926.       }
  927.     }
  928.     
  929.     // Something is terribly wrong - no layouts are registered for this skin
  930.     if (!defaultLayoutURL) {
  931.       throw Components.results.NS_ERROR_FAILURE;
  932.     }
  933.     
  934.     return defaultLayoutURL;
  935.   },
  936.     
  937.   /**
  938.    * \sa sbIFeathersManager
  939.    */
  940.   isChromeEnabled: function isChromeEnabled(layoutURL, internalName) {
  941.     // TEMP fix for the Mac to enable the titlebar on the main window.
  942.     // See Bug 4363
  943.     var sysInfo = Cc["@mozilla.org/system-info;1"]
  944.                             .getService(Ci.nsIPropertyBag2);
  945.     var platform = sysInfo.getProperty("name");
  946.     
  947.     if (this._mappings[layoutURL]) {
  948.       if (this._mappings[layoutURL][internalName]) {
  949.         return this._mappings[layoutURL][internalName].showChrome == true;
  950.       }
  951.     }
  952.    
  953.     return false; 
  954.   },
  955.  
  956.  
  957.   getFeatherPrefBranch: function getFeatherPrefBranch (layoutURL, internalName) {
  958.     var prefs = Cc["@mozilla.org/preferences-service;1"]
  959.       .getService(Ci.nsIPrefService);
  960.  
  961.     // a really simple url escaping algorithm
  962.     // turn all non-alphanumeric characters into:
  963.     //   "_" upper case hex charactre code "_"
  964.     function escape_url(url) {
  965.       return url.replace(/[^a-zA-Z0-9]/g, 
  966.           function(c) { 
  967.             return '_'+(c.charCodeAt(0).toString(16)).toUpperCase()+'_'; });
  968.     }
  969.  
  970.     var branchName = 'songbird.feather.' +
  971.       (internalName?internalName:'null') + '.' +
  972.       (layoutURL?escape_url(layoutURL):'null') + '.';
  973.  
  974.     return prefs.getBranch(branchName);
  975.   },
  976.  
  977.  
  978.   canOnTop: function canOnTop(layoutURL, internalName) {
  979.     if (this._mappings[layoutURL]) {
  980.       if (this._mappings[layoutURL][internalName]) {
  981.         return this._mappings[layoutURL][internalName].onTop == true;
  982.       }
  983.     }
  984.    
  985.     return false; 
  986.   },
  987.  
  988.  
  989.   isOnTop: function isOnTop(layoutURL, internalName) {
  990.     if (!this.canOnTop(layoutURL, internalName)) {
  991.       return false;
  992.     }
  993.  
  994.     var prefBranch = this.getFeatherPrefBranch(layoutURL, null);
  995.     if (prefBranch.prefHasUserValue('on_top')) {
  996.       return prefBranch.getBoolPref('on_top');
  997.     }
  998.     
  999.     return false;
  1000.   },
  1001.  
  1002.  
  1003.   setOnTop: function setOnTop(layoutURL, internalName, onTop) {
  1004.     if (!this.canOnTop(layoutURL, internalName)) {
  1005.       return false;
  1006.     }
  1007.  
  1008.     var prefBranch = this.getFeatherPrefBranch(layoutURL, null);
  1009.     prefBranch.setBoolPref('on_top', onTop);
  1010.  
  1011.     return;
  1012.   },
  1013.  
  1014.  
  1015.   /* FIXME: add the ability to observe onTop state */
  1016.  
  1017.  
  1018.   /**
  1019.    * \sa sbIFeathersManager
  1020.    */
  1021.   getSkinsForLayout: function getSkinsForLayout(layoutURL) {
  1022.     var skins = [];
  1023.     
  1024.     // Find skin descriptions that are compatible with the given layout.
  1025.     if (this._mappings[layoutURL]) {
  1026.       for (internalName in this._mappings[layoutURL]) {
  1027.         var desc = this.getSkinDescription(internalName);
  1028.         if (desc) {
  1029.           skins.push(desc);
  1030.         }
  1031.       }
  1032.     }   
  1033.     return new ArrayEnumerator( skins );
  1034.   },
  1035.   
  1036.   
  1037.   /**
  1038.    * \sa sbIFeathersManager
  1039.    */
  1040.   getLayoutsForSkin: function getLayoutsForSkin(internalName) {
  1041.     return new ArrayEnumerator( this._getLayoutsArrayForSkin(internalName) );
  1042.   },
  1043.  
  1044.  
  1045.   /**
  1046.    * \sa sbIFeathersManager
  1047.    */
  1048.   switchFeathers: function switchFeathers(layoutURL, internalName) {
  1049.     // don't allow this call if we're already switching
  1050.     if (this._switching) {
  1051.       return;
  1052.     }
  1053.  
  1054.     layoutDescription = this.getLayoutDescription(layoutURL);
  1055.     skinDescription = this.getSkinDescription(internalName);
  1056.     
  1057.     // Make sure we know about the requested skin and layout
  1058.     if (layoutDescription == null || skinDescription == null) {
  1059.       throw new Components.Exception("Unknown layout/skin passed to switchFeathers");
  1060.     }
  1061.     
  1062.     // Check compatibility.
  1063.     // True/false refer to the showChrome value, so check for undefined
  1064.     // to determine compatibility.
  1065.     if (this._mappings[layoutURL][internalName] === undefined) {
  1066.       throw new Components.Exception("Skin [" + internalName + "] and Layout [" + layoutURL +
  1067.             " are not compatible");
  1068.     } 
  1069.     
  1070.     // Notify that a select is about to occur
  1071.     this._onSelect(layoutDescription, skinDescription);
  1072.     
  1073.     // Remember the current feathers so that we can revert later if needed
  1074.     this._previousLayoutDataRemote.stringValue = this.currentLayoutURL;
  1075.     this._previousSkinDataRemote.stringValue = this.currentSkinName;
  1076.  
  1077.     // Make sure the application doesn't exit because we closed the last window
  1078.     var appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
  1079.                        .getService(Ci.nsIAppStartup);
  1080.     appStartup.enterLastWindowClosingSurvivalArea();
  1081.  
  1082.     try {
  1083.       // close the player window *before* changing the skin
  1084.       // otherwise Gecko tries to load an image that will go away right after and crashes
  1085.       // (songbird bug 3965)
  1086.       this._closePlayerWindow(internalName == this.currentSkinName);
  1087.       
  1088.       var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1089.       var callback = new FeathersManager_switchFeathers_callback(this, layoutURL, internalName);
  1090.       this._switching = true;
  1091.       timer.initWithCallback(callback, 0, Ci.nsITimer.TYPE_ONE_SHOT);
  1092.     } catch (e) {
  1093.       // If we failed make sure to allow the application to quit again
  1094.       appStartup.exitLastWindowClosingSurvivalArea();
  1095.       throw e;
  1096.     }
  1097.   },
  1098.   
  1099.   /**
  1100.    * \sa sbIFeathersManager
  1101.    */
  1102.   switchToNextLayout: function switchToNextLayout() {
  1103.     var curSkinName = this.currentSkinName;
  1104.     var curLayoutURL = this.currentLayoutURL;
  1105.     
  1106.     // Find the next layout (if one exists):
  1107.     var nextLayout;
  1108.     var layouts = this._getLayoutsArrayForSkin(curSkinName);
  1109.     for (var i = 0; i < layouts.length; i++) {
  1110.       if (layouts[i].url == curLayoutURL) {
  1111.         if (i >= layouts.length - 1) {
  1112.           nextLayout = layouts[0];
  1113.         } 
  1114.         else {
  1115.           nextLayout = layouts[i+1];
  1116.         }
  1117.       }
  1118.     }
  1119.     
  1120.     if (nextLayout != null && nextLayout.url != curLayoutURL) {
  1121.       this.switchFeathers(nextLayout.url, curSkinName);
  1122.     }
  1123.   },
  1124.   
  1125.   
  1126.   /**
  1127.    * \sa sbIFeathersManager
  1128.    * Relaunch the main window
  1129.    */
  1130.   openPlayerWindow: function openPlayerWindow() {
  1131.     // First, check if we should auto switch to a new skin/layout
  1132.     // (but only if we're not already in the middle of a switch)
  1133.     if (this._autoswitch && !this._switching) {
  1134.       this._layoutDataRemote.stringValue = this._autoswitch.layoutURL;
  1135.       this._skinDataRemote.stringValue = this._autoswitch.skin;
  1136.       this._autoswitch = null;
  1137.     } 
  1138.     
  1139.     // Check to make sure the current feathers are valid
  1140.     var layoutDescription = this.getLayoutDescription(this.currentLayoutURL);
  1141.     var skinDescription = this.getSkinDescription(this.currentSkinName);
  1142.     if (layoutDescription == null || skinDescription == null) {
  1143.       // The current feathers are invalid. Switch to the defaults.
  1144.       this.switchFeathers(this._defaultLayoutURL, this._defaultSkinName);
  1145.       return;
  1146.     }
  1147.  
  1148.     // Check to see if we are in test mode, if so, we don't actually
  1149.     // want to open the window as it will break the testing we're 
  1150.     // attempting to do.    
  1151.     if(SBDataGetBoolValue(DATAREMOTE_TESTMODE)) {
  1152.       // Indicate to the console and jsconsole that we are test mode.
  1153.       dump("FeathersManager.openPlayerWindow: In Test Mode, no window will be open!\n");
  1154.       Cu.reportError("FeathersManager.openPlayerWindow: In Test Mode, no window will be open\n");
  1155.       return;
  1156.     }
  1157.  
  1158.     // Determine window features.  If chrome is enabled, make resizable.
  1159.     // Otherwise remove the titlebar.
  1160.     var chromeFeatures = "chrome,modal=no,resizable=yes,toolbar=yes,popup=no";
  1161.     
  1162.     // on windows and mac, centerscreen gets overriden by persisted position.
  1163.     // not so for linux.
  1164.     var runtimeInfo = Components.classes["@mozilla.org/xre/app-info;1"]
  1165.                                 .getService(Components.interfaces.nsIXULRuntime);
  1166.     switch (runtimeInfo.OS) {
  1167.       case "WINNT":
  1168.       case "Darwin":
  1169.         chromeFeatures += ",centerscreen";
  1170.     }
  1171.     
  1172.     var showChrome = this.isChromeEnabled(this.currentLayoutURL, this.currentSkinName);
  1173.     if (showChrome) {
  1174.        chromeFeatures += ",titlebar=yes";
  1175.     } else {
  1176.        chromeFeatures += ",titlebar=no";
  1177.     }
  1178.     
  1179.     // Set the global chrome (window border and title) flag
  1180.     this._setChromeEnabled(showChrome);
  1181.     
  1182.     // Open the new player window
  1183.     var windowWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]
  1184.                           .getService(Ci.nsIWindowWatcher);
  1185.                           
  1186.     var newMainWin = windowWatcher.openWindow(null,
  1187.                                               this.currentLayoutURL, 
  1188.                                               "", 
  1189.                                               chromeFeatures,
  1190.                                               null);
  1191.     newMainWin.focus();
  1192.   },
  1193.   
  1194.   
  1195.   /**
  1196.    * \sa sbIFeathersManager
  1197.    */  
  1198.   addListener: function addListener(listener) {
  1199.     if (! (listener instanceof Ci.sbIFeathersManagerListener))
  1200.     {
  1201.       throw Components.results.NS_ERROR_INVALID_ARG;
  1202.     }
  1203.     this._listeners.push(listener);
  1204.   },
  1205.   
  1206.   /**
  1207.    * \sa sbIFeathersManager
  1208.    */  
  1209.   removeListener: function removeListener(listener) {
  1210.     var index = this._listeners.indexOf(listener);
  1211.     if (index > -1) {
  1212.       this._listeners.splice(index,1);
  1213.     }
  1214.   },
  1215.   
  1216.   
  1217.   /**
  1218.    * Get an array of the layouts for the current skin.
  1219.    */
  1220.   _getLayoutsArrayForSkin: function _getLayoutsArrayForSkin(internalName) {
  1221.     var layouts = [];
  1222.     
  1223.     // Find skin descriptions that are compatible with the given layout.
  1224.     for (var layout in this._mappings) {
  1225.       if (internalName in this._mappings[layout]) {
  1226.         var desc = this.getLayoutDescription(layout);
  1227.         if (desc) {
  1228.           layouts.push(desc);
  1229.         }      
  1230.       }
  1231.     }
  1232.     
  1233.     return layouts;
  1234.   },
  1235.  
  1236.  
  1237.   /**
  1238.    * Close all player windows (except the plugin host)
  1239.    *
  1240.    * @param aLayoutSwitchOnly if true, we will not close windows that wish to
  1241.    *                          not be closed on layout changes
  1242.    * 
  1243.    */
  1244.   _closePlayerWindow: function _closePlayerWindow(aLayoutSwitchOnly) {
  1245.     // Check to see if we are in test mode, if so, we don't actually
  1246.     // want to open the window as it will break the testing we're 
  1247.     // attempting to do.    
  1248.     if(SBDataGetBoolValue(DATAREMOTE_TESTMODE)) {
  1249.       // Indicate to the console and jsconsole that we are test mode.
  1250.       dump("FeathersManager.openPlayerWindow: In Test Mode\n");
  1251.       Cu.reportError("FeathersManager.openPlayerWindow: In Test Mode\n");
  1252.       return;
  1253.     }
  1254.  
  1255.     var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]
  1256.                                    .getService(Ci.nsIWindowMediator);
  1257.  
  1258.     // Close all open windows other than the core, dominspector, and venkman.
  1259.     // This is needed in order to reset window chrome settings.
  1260.     var playerWindows = windowMediator.getEnumerator(null);
  1261.     while (playerWindows.hasMoreElements()) {
  1262.       var window = playerWindows.getNext();
  1263.       if (!window) {
  1264.         continue;
  1265.       }
  1266.  
  1267.       // Don't close DOMi or other debug windows... that's just annoying
  1268.       try {
  1269.         let docElement = window.document.documentElement;
  1270.         // Go by the window ID instead of URL for these, in case we end up with
  1271.         // things that try to override them (e.g. Error2)
  1272.         switch (docElement.getAttribute("id")) {
  1273.           case "JSConsoleWindow":
  1274.           case "winInspectorMain":
  1275.           case "venkman-window":
  1276.             // don't close these
  1277.             continue;
  1278.         }
  1279.         if (aLayoutSwitchOnly) {
  1280.           // this is a layout switch, the skin will be the same;
  1281.           // check for opt-in attribute and skip those
  1282.           if (docElement.hasAttribute("sb-no-close-on-layout-switch")) {
  1283.             // don't close this either
  1284.             continue;
  1285.           }
  1286.         }
  1287.       } catch (e) {
  1288.         /* ignore any errors - assume they're closable */
  1289.       }
  1290.  
  1291.       window.close();
  1292.     }
  1293.   },
  1294.  
  1295.       
  1296.   /**
  1297.    * Indicates to the rest of the system whether or not to 
  1298.    * enable titlebars when opening windows
  1299.    */
  1300.   _setChromeEnabled: function _setChromeEnabled(enabled) {
  1301.  
  1302.     // Set the global chrome (window border and title) flag
  1303.     this._showChromeDataRemote.boolValue = enabled;
  1304.  
  1305.     var prefs = Cc["@mozilla.org/preferences-service;1"]
  1306.                           .getService(Ci.nsIPrefBranch);
  1307.  
  1308.     // Set the flags used to open the core window on startup.
  1309.     // Do a replacement in order to preserve whatever other features 
  1310.     // were specified.
  1311.     try {
  1312.       var titlebarRegEx = /(titlebar=)(no|yes)/;
  1313.       var replacement = (enabled) ? "$1yes" : "$1no";
  1314.       var defaultChromeFeatures = prefs.getCharPref("toolkit.defaultChromeFeatures");
  1315.       prefs.setCharPref("toolkit.defaultChromeFeatures",
  1316.               defaultChromeFeatures.replace(titlebarRegEx, replacement));
  1317.     } catch (e) {
  1318.       Cu.reportError("FeathersManager._setChromeEnabled: Error setting " + 
  1319.                      "defaultChromeFeatures pref!\n" + e);
  1320.     }
  1321.   },      
  1322.       
  1323.  
  1324.   /**
  1325.    * Broadcasts an update event to all registered listeners
  1326.    */
  1327.   _onUpdate: function onUpdate() {
  1328.     this._listeners.forEach( function (listener) {
  1329.       listener.onFeathersUpdate();
  1330.     });
  1331.   },
  1332.  
  1333.  
  1334.   /**
  1335.    * Broadcasts an select (feathers switch) event to all registered listeners
  1336.    */
  1337.   _onSelect: function onSelect(layoutDesc, skinDesc) {
  1338.     // Verify args
  1339.     layoutDesc = layoutDesc.QueryInterface(Ci.sbILayoutDescription);
  1340.     skinDesc = skinDesc.QueryInterface(Ci.sbISkinDescription);
  1341.     
  1342.     // Broadcast notification
  1343.     this._listeners.forEach( function (listener) {
  1344.       listener.onFeathersSelectRequest(layoutDesc, skinDesc);
  1345.     });
  1346.   },
  1347.  
  1348.   _onSelectComplete: function onSelectComplete() {
  1349.     var layoutDescription = this.getLayoutDescription(this.currentLayoutURL);
  1350.     var skinDescription = this.getSkinDescription(this.currentSkinName);
  1351.  
  1352.     // Broadcast notification
  1353.     this._listeners.forEach( function (listener) {
  1354.       listener.onFeathersSelectComplete(layoutDescription, skinDescription);
  1355.     });
  1356.   },
  1357.  
  1358.   /**
  1359.    * Called by the observer service. Looks for XRE shutdown messages 
  1360.    */
  1361.   observe: function(subject, topic, data) {
  1362.     var os      = Cc["@mozilla.org/observer-service;1"]
  1363.                       .getService(Ci.nsIObserverService);
  1364.     switch (topic) {
  1365.     case "quit-application":
  1366.       this._deinit();
  1367.       break;
  1368.  
  1369.     case "final-ui-startup":
  1370.       this._init();
  1371.       break;
  1372.     }
  1373.   },
  1374.  
  1375.   /**
  1376.    * Check if an addon is disabled, and if so, re-enables it.
  1377.    * Note that this only checks for the userDisabled flag,
  1378.    * not appDisabled, so addons that have been disabled because
  1379.    * of a compatibility or security issue remain disabled.
  1380.    */
  1381.   _ensureAddOnEnabled: function(id) {
  1382.     const nsIUpdateItem = Ci.nsIUpdateItem;
  1383.     var em = Cc["@mozilla.org/extensions/manager;1"]
  1384.                .getService(Ci.nsIExtensionManager);
  1385.     var ds = em.datasource; 
  1386.     var rdf = Cc["@mozilla.org/rdf/rdf-service;1"]
  1387.                 .getService(Ci.nsIRDFService);
  1388.  
  1389.     var resource = rdf.GetResource("urn:mozilla:item:" + id); 
  1390.  
  1391.     var property = rdf.GetResource("http://www.mozilla.org/2004/em-rdf#userDisabled");
  1392.     var target = ds.GetTarget(resource, property, true);
  1393.  
  1394.     function getData(literalOrResource) {
  1395.       if (literalOrResource instanceof Ci.nsIRDFLiteral ||
  1396.           literalOrResource instanceof Ci.nsIRDFResource ||
  1397.           literalOrResource instanceof Ci.nsIRDFInt)
  1398.         return literalOrResource.Value;
  1399.       return undefined;
  1400.     }
  1401.  
  1402.     var userDisabled = getData(target);
  1403.       
  1404.     if (userDisabled == "true") {
  1405.       em.enableItem(id); 
  1406.     }
  1407.   }, 
  1408.  
  1409.   /**
  1410.    * See nsISupports.idl
  1411.    */
  1412.   QueryInterface:
  1413.     XPCOMUtils.generateQI([IID, Components.interfaces.nsIObserver]),
  1414. }; // FeathersManager.prototype
  1415.  
  1416. /**
  1417.  * Callback helper for FeathersManager::switchFeathers
  1418.  * This is needed to make sure the window is really closed before we switch skins
  1419.  */
  1420. function FeathersManager_switchFeathers_callback(aFeathersManager,
  1421.                                                  aLayoutURL,
  1422.                                                  aInternalName) {
  1423.   this.feathersManager = aFeathersManager;
  1424.   this.layoutURL = aLayoutURL;
  1425.   this.internalName = aInternalName;
  1426. }
  1427.  
  1428. FeathersManager_switchFeathers_callback.prototype = {
  1429.   /**
  1430.    * \sa nsITimerCallback
  1431.    */
  1432.   notify: function FeathersManager_switchFeathers_callback_notify() {
  1433.     try {
  1434.       // Set new values
  1435.       this.feathersManager._layoutDataRemote.stringValue = this.layoutURL;
  1436.       this.feathersManager._skinDataRemote.stringValue = this.internalName;
  1437.  
  1438.       // Flush all chrome caches
  1439.       Cc["@mozilla.org/observer-service;1"]
  1440.         .getService(Ci.nsIObserverService)
  1441.         .notifyObservers(null, "chrome-flush-caches", null);
  1442.  
  1443.       // Reload our agent sheet
  1444.       var styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
  1445.                                 .getService(Ci.nsIStyleSheetService);
  1446.       styleSheetService.unregisterSheet(this.feathersManager._agentSheetURI,
  1447.                                         styleSheetService.AGENT_SHEET);
  1448.       styleSheetService.loadAndRegisterSheet(this.feathersManager._agentSheetURI,
  1449.                                              styleSheetService.AGENT_SHEET);
  1450.   
  1451.       this.feathersManager.openPlayerWindow();
  1452.       this.feathersManager._switching = false;
  1453.       this.feathersManager._onSelectComplete();
  1454.       this.feathersManager = null;
  1455.     } finally {
  1456.       // After the callback the application should always be able to quit again
  1457.       var appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
  1458.                          .getService(Ci.nsIAppStartup);
  1459.       appStartup.exitLastWindowClosingSurvivalArea();
  1460.     }
  1461.   }
  1462. }; // FeathersManager_switchFeathers_callback.prototype
  1463.  
  1464.  
  1465.  
  1466.  
  1467.  
  1468. /**
  1469.  * ----------------------------------------------------------------------------
  1470.  * Registration for XPCOM
  1471.  * ----------------------------------------------------------------------------
  1472.  */
  1473.  
  1474. function NSGetModule(comMgr, fileSpec) {
  1475.   return XPCOMUtils.generateModule([FeathersManager]);
  1476. } // NSGetModule
  1477.  
  1478.  
  1479.  
  1480.