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