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

  1. /** vim: ts=2 sw=2 expandtab
  2. //
  3. // BEGIN SONGBIRD GPL
  4. //
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 POTI, Inc.
  8. // http://songbirdnest.com
  9. //
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. //
  13. // Software distributed under the License is distributed
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
  15. // express or implied. See the GPL for the specific language
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc.,
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. //
  23. // END SONGBIRD GPL
  24. //
  25.  */
  26.  
  27. /**
  28.  * \file ServicePaneService.js
  29.  * \brief the service pane service manages the tree behind the service pane
  30.  *
  31.  * TODO:
  32.  *  - implement lazy saving of dirty datasources.
  33.  *  - handle module removal
  34.  *
  35.  */
  36.  
  37. function DEBUG(msg) {
  38.   //dump('sbServicePaneService.js: '+msg+'\n');
  39. }
  40.  
  41. const Cc = Components.classes;
  42. const Ci = Components.interfaces;
  43. const Cr = Components.results;
  44. const Ce = Components.Exception;
  45.  
  46. const RDFSVC = Cc['@mozilla.org/rdf/rdf-service;1'].getService(Ci.nsIRDFService);
  47. const RDFCU = Cc['@mozilla.org/rdf/container-utils;1'].getService(Ci.nsIRDFContainerUtils);
  48. const IOSVC = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
  49. const gPrefs = Cc['@mozilla.org/preferences-service;1'].getService(Ci.nsIPrefBranch);
  50.  
  51. const NC='http://home.netscape.com/NC-rdf#';
  52. const SP='http://songbirdnest.com/rdf/servicepane#';
  53.  
  54. const STRINGBUNDLE='chrome://songbird/locale/songbird.properties';
  55.  
  56. function ServicePaneNode (ds, resource) {
  57.   this._resource = resource;
  58.  
  59.   this._dataSource = ds;
  60.   this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource);
  61.  
  62.   // set up this._container if this is a Seq
  63.   if (RDFCU.IsSeq(ds, resource)) {
  64.     this._container = Cc['@mozilla.org/rdf/container;1'].createInstance(Ci.nsIRDFContainer);
  65.     this._container.Init(ds, resource);
  66.     this._resource = null;
  67.   } else {
  68.     this._container = null;
  69.   }
  70. }
  71.  
  72. ServicePaneNode.prototype.QueryInterface = function (iid) {
  73.   if (!iid.equals(Ci.sbIServicePaneNode) &&
  74.     !iid.equals(Ci.sbIServicePaneNodeInternal) &&
  75.     !iid.equals(Ci.nsIClassInfo) &&
  76.     !iid.equals(Ci.nsISupports)) {
  77.     throw Components.results.NS_ERROR_NO_INTERFACE;
  78.   }
  79.   return this;
  80. }
  81. ServicePaneNode.prototype.classDescription = 'Songbird Service Pane Node';
  82. ServicePaneNode.prototype.classID = null;
  83. ServicePaneNode.prototype.contractID = null;
  84. ServicePaneNode.prototype.flags = Ci.nsIClassInfo.MAIN_THREAD_ONLY;
  85. ServicePaneNode.prototype.implementationLanguage = Ci.nsIProgrammingLanguage.JAVASCRIPT;
  86. ServicePaneNode.prototype.getHelperForLanguage = function(aLanguage) { return null; }
  87. ServicePaneNode.prototype.getInterfaces = function(count) {
  88.   var interfaces = [Ci.sbIServicePaneNode,
  89.             Ci.sbIServicePaneNodeInternal,
  90.             Ci.nsIClassInfo,
  91.             Ci.nsISupports,
  92.            ];
  93.   count.value = interfaces.length;
  94.   return interfaces;
  95. }
  96.  
  97. ServicePaneNode.prototype.__defineGetter__ ('resource',
  98.   function () {
  99.     if (this._container) {
  100.       return this._container.Resource;
  101.     }
  102.     else {
  103.       return this._resource;
  104.     }
  105.   }
  106. );
  107.  
  108. ServicePaneNode.prototype.__defineGetter__ ('id',
  109.     function () { return this.resource.ValueUTF8; });
  110.  
  111. ServicePaneNode.prototype.__defineGetter__ ('isContainer',
  112.     function () { return this._container != null });
  113.  
  114. ServicePaneNode.prototype.setAttributeNS = function (aNamespace, aName, aValue) {
  115.   var property = RDFSVC.GetResource(aNamespace+aName);
  116.   var target = this._dataSource.GetTarget(this.resource, property, true);
  117.   if (target) {
  118.     this._dataSource.Unassert(this.resource, property, target);
  119.   }
  120.   if (aValue != null) {
  121.     this._dataSource.Assert(this.resource, property,
  122.                 RDFSVC.GetLiteral(aValue), true);
  123.   }
  124. }
  125.  
  126. ServicePaneNode.prototype.getAttributeNS = function (aNamespace, aName) {
  127.   var property = RDFSVC.GetResource(aNamespace+aName);
  128.   var target = this._dataSource.GetTarget(this.resource, property, true);
  129.   if (target) {
  130.     var value = target.QueryInterface(Ci.nsIRDFLiteral).Value
  131.     return value;
  132.   } else {
  133.     return null;
  134.   }
  135. }
  136.  
  137. ServicePaneNode.prototype.__defineGetter__ ('url', function () {
  138.   return this.getAttributeNS(NC,'URL'); })
  139. ServicePaneNode.prototype.__defineSetter__ ('url', function (aValue) {
  140.   this.setAttributeNS(NC,'URL', aValue); });
  141.  
  142. ServicePaneNode.prototype.__defineGetter__ ('image', function () {
  143.   return this.getAttributeNS(NC,'Icon'); })
  144. ServicePaneNode.prototype.__defineSetter__ ('image', function (aValue) {
  145.   this.setAttributeNS(NC,'Icon', aValue); });
  146.  
  147. ServicePaneNode.prototype.__defineGetter__ ('name', function () {
  148.   return this.getAttributeNS(NC,'Name'); })
  149. ServicePaneNode.prototype.__defineSetter__ ('name', function (aValue) {
  150.   this.setAttributeNS(NC,'Name', aValue); });
  151.  
  152. ServicePaneNode.prototype.__defineGetter__ ('tooltip', function () {
  153.   return this.getAttributeNS(NC,'Description'); })
  154. ServicePaneNode.prototype.__defineSetter__ ('tooltip', function (aValue) {
  155.   this.setAttributeNS(NC,'Description', aValue); });
  156.  
  157. ServicePaneNode.prototype.__defineGetter__ ('properties', function () {
  158.   return this.getAttributeNS(SP,'Properties'); })
  159. ServicePaneNode.prototype.__defineSetter__ ('properties', function (aValue) {
  160.  this.setAttributeNS(SP,'Properties', aValue); });
  161.  
  162. ServicePaneNode.prototype.__defineGetter__ ('contractid', function () {
  163.   return this.getAttributeNS(SP,'contractid'); })
  164. ServicePaneNode.prototype.__defineSetter__ ('contractid', function (aValue) {
  165.   this.setAttributeNS(SP,'contractid', aValue); });
  166.  
  167. ServicePaneNode.prototype.__defineGetter__ ('hidden', function () {
  168.   return this.getAttributeNS(SP,'Hidden') == 'true'; })
  169. ServicePaneNode.prototype.__defineSetter__ ('hidden', function (aValue) {
  170.   this.setAttributeNS(SP,'Hidden', aValue?'true':'false'); });
  171.  
  172. ServicePaneNode.prototype.__defineGetter__ ('editable', function () {
  173.   return this.getAttributeNS(SP,'Editable') == 'true'; })
  174. ServicePaneNode.prototype.__defineSetter__ ('editable', function (aValue) {
  175.   this.setAttributeNS(SP,'Editable', aValue?'true':'false'); });
  176.  
  177. ServicePaneNode.prototype.__defineGetter__ ('isOpen', function () {
  178.   return this.getAttributeNS(SP,'Open') == 'true'; })
  179. ServicePaneNode.prototype.__defineSetter__ ('isOpen', function (aValue) {
  180.   this.setAttributeNS(SP,'Open', aValue?'true':'false'); });
  181.  
  182. ServicePaneNode.prototype.__defineGetter__ ('dndDragTypes', function () {
  183.   return this.getAttributeNS(SP,'dndDragTypes'); })
  184. ServicePaneNode.prototype.__defineSetter__ ('dndDragTypes', function (aValue) {
  185.   this.setAttributeNS(SP,'dndDragTypes', aValue); });
  186.  
  187. ServicePaneNode.prototype.__defineGetter__ ('dndAcceptNear', function () {
  188.   return this.getAttributeNS(SP,'dndAcceptNear'); })
  189. ServicePaneNode.prototype.__defineSetter__ ('dndAcceptNear', function (aValue) {
  190.   this.setAttributeNS(SP,'dndAcceptNear', aValue); });
  191.  
  192. ServicePaneNode.prototype.__defineGetter__ ('dndAcceptIn', function () {
  193.   return this.getAttributeNS(SP,'dndAcceptIn'); })
  194. ServicePaneNode.prototype.__defineSetter__ ('dndAcceptIn', function (aValue) {
  195.   this.setAttributeNS(SP,'dndAcceptIn', aValue); });
  196.  
  197. ServicePaneNode.prototype.__defineGetter__ ('stringbundle', function () {
  198.   return this.getAttributeNS(SP,'stringbundle'); })
  199. ServicePaneNode.prototype.__defineSetter__ ('stringbundle', function (aValue) {
  200.   this.setAttributeNS(SP,'stringbundle', aValue); });
  201.  
  202.  
  203. ServicePaneNode.prototype.__defineGetter__('childNodes',
  204.     function() {
  205.       if (!this.isContainer) {
  206.         throw this.id+' is not a container';
  207.       }
  208.       var e = this._container.GetElements();
  209.       var ds = this._dataSource;
  210.       return {
  211.         hasMoreElements: function() { return e.hasMoreElements(); },
  212.         getNext: function() {
  213.           if (!e.hasMoreElements()) { return null; }
  214.           var resource = e.getNext();
  215.           if (!resource) { return null; }
  216.           return new ServicePaneNode(ds,
  217.               resource.QueryInterface(Ci.nsIRDFResource));
  218.         }
  219.       };
  220.     });
  221.  
  222. ServicePaneNode.prototype.__defineGetter__('firstChild',
  223.     function () {
  224.       return this.childNodes.getNext();
  225.     });
  226.  
  227. ServicePaneNode.prototype.__defineGetter__('lastChild',
  228.     function () {
  229.       var enumerator = this.childNodes;
  230.       var node = null;
  231.       while (enumerator.hasMoreElements()) {
  232.         node = enumerator.getNext();
  233.       }
  234.       return node;
  235.     });
  236.  
  237. ServicePaneNode.prototype.__defineGetter__('nextSibling',
  238.     function () {
  239.       var parent = this.parentNode;
  240.       if (!parent) {
  241.         // can't have siblings without parents
  242.         return null;
  243.       }
  244.       var nodes = parent.childNodes;
  245.       while (nodes.hasMoreElements()) {
  246.         var node = nodes.getNext();
  247.         if (node.id == this.id) {
  248.           return nodes.getNext();
  249.         }
  250.       }
  251.       return null;
  252.     });
  253.  
  254. ServicePaneNode.prototype.__defineGetter__('parentNode',
  255.     function () {
  256.       var labels = this._dataSource.ArcLabelsIn(this.resource);
  257.       while (labels.hasMoreElements()) {
  258.         var label = labels.getNext().QueryInterface(Ci.nsIRDFResource);
  259.         if (RDFCU.IsOrdinalProperty(label)) {
  260.           return new ServicePaneNode(this._dataSource,
  261.               this._dataSource.GetSource(label, this.resource, true));
  262.         }
  263.       }
  264.       return null;
  265.     });
  266.  
  267. ServicePaneNode.prototype.__defineGetter__('previousSibling',
  268.     function () {
  269.       var parent = this.parentNode;
  270.       if (!parent) {
  271.         // can't have siblings without parents
  272.         return null;
  273.       }
  274.       var prev = null;
  275.       var nodes = parent.childNodes;
  276.       while (nodes.hasMoreElements()) {
  277.         var node = nodes.getNext();
  278.         if (node.id == this.id) {
  279.           return prev;
  280.         }
  281.         prev = node;
  282.       }
  283.       return null;
  284.     });
  285.  
  286. ServicePaneNode.prototype.appendChild = function(aChild) {
  287.   if (!this.isContainer) {
  288.     throw this.id+' is not a container';
  289.   }
  290.  
  291.   aChild.unlinkNode();
  292.  
  293.   this._container.AppendElement(aChild.resource);
  294.   return aChild;
  295. };
  296.  
  297. ServicePaneNode.prototype.insertBefore = function(aNewNode, aAdjacentNode) {
  298.   DEBUG('insertBefore('+aNewNode.id+', '+aAdjacentNode.id+')');
  299.  
  300.   if (!this.isContainer) {
  301.     throw this.id + ' is not a container';
  302.   }
  303.  
  304.   if (this.id != aAdjacentNode.parentNode.id) {
  305.     throw aAdjacentNode.id + ' is not in ' + this.id;
  306.   }
  307.  
  308.   // unlink the node we're moving from where it is currently
  309.   aNewNode.unlinkNode();
  310.  
  311.   // work out where it should go
  312.   var index = this._container.IndexOf(aAdjacentNode.resource);
  313.  
  314.   DEBUG(' index='+index);
  315.  
  316.   // add it back in there
  317.   this._container.InsertElementAt(aNewNode.resource, index, true);
  318. }
  319.  
  320. ServicePaneNode.prototype.removeChild = function(aChild) {
  321.   if (!this.isContainer) {
  322.     throw this.id + ' is not a container';
  323.   }
  324.  
  325.   if (this._container.IndexOf(aChild.resource)< 0) {
  326.     throw aChild.id + ' is not in ' + this.id;
  327.   }
  328.  
  329.   this._container.RemoveElement(aChild.resource, true);
  330.  
  331.   aChild.clearNode();
  332. }
  333.  
  334. ServicePaneNode.prototype.replaceChild = function(aNewNode, aOldNode) {
  335.   if (!this.isContainer) {
  336.     throw this.id + ' is not a container';
  337.   }
  338.  
  339.   var index = this._container.IndexOf(aOldNode.resource);
  340.   if (index < 0) {
  341.     throw aOldNode.id + ' is not in ' + this.id;
  342.   }
  343.  
  344.   this.insertBefore(aNewNode, aOldNode);
  345.   this.removeChild(aOldNode);
  346. }
  347.  
  348. ServicePaneNode.prototype.unlinkChild = function (aChild) {
  349.   // fail silently
  350.   if (this._container.IndexOf(aChild.resource) < 0) {
  351.     return;
  352.   }
  353.   this._container.RemoveElement(aChild.resource, true);
  354. }
  355.  
  356. ServicePaneNode.prototype.unlinkNode = function () {
  357.   var parent = this.parentNode;
  358.   if (parent) {
  359.     parent.unlinkChild(this);
  360.   }
  361. }
  362.  
  363. ServicePaneNode.prototype.clearNode = function () {
  364.   if (this.isContainer) {
  365.     // first we need to remove all our children from the graph
  366.     var children = this.childNodes;
  367.     while (children.hasMoreElements()) {
  368.       this.removeChild(children.getNext());
  369.     }
  370.   }
  371.  
  372.   // then we need to find all our outgoing arcs
  373.   var arcs = this._dataSource.ArcLabelsOut(this.resource);
  374.   while (arcs.hasMoreElements()) {
  375.     var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource);
  376.     // get the targets
  377.     var targets = this._dataSource.GetTargets(this.resource, arc, true);
  378.     while (targets.hasMoreElements()) {
  379.       var target = targets.getNext().QueryInterface(Ci.nsIRDFNode);
  380.       // and remove them
  381.       this._dataSource.Unassert(this.resource, arc, target);
  382.     }
  383.   }
  384. }
  385.  
  386.  
  387.  
  388.  
  389.  
  390. function ServicePaneService () {
  391.   DEBUG('ServicePaneService() starts');
  392.   this._initialized = false;
  393.   var obsSvc = Cc['@mozilla.org/observer-service;1']
  394.     .getService(Ci.nsIObserverService);
  395.   obsSvc.addObserver(this, 'quit-application', false);
  396.   DEBUG('ServicePaneService() ends');
  397. }
  398. ServicePaneService.prototype.observe = /* for nsIObserver */
  399. function ServicePaneService_observe(subject, topic, data) {
  400.   DEBUG('ServicePaneService.observe() begins');
  401.   if (topic == 'quit-application') {
  402.     var obsSvc = Cc['@mozilla.org/observer-service;1']
  403.       .getService(Ci.nsIObserverService);
  404.     obsSvc.removeObserver(this, 'quit-application');
  405.     this.shutdown();
  406.   }
  407.   DEBUG('ServicePaneService.observe() ends');
  408. }
  409. ServicePaneService.prototype.init = function ServicePaneService_init() {
  410.   DEBUG('ServicePaneService.init() begins');
  411.  
  412.   if (this._initialized) {
  413.     // already initialized
  414.     DEBUG('already initialized');
  415.     return;
  416.   }
  417.   this._initialized = true;
  418.  
  419.   var dirsvc = Cc['@mozilla.org/file/directory_service;1'].getService(Ci.nsIProperties);
  420.  
  421.   // get the profile directory
  422.   var bResult = {};
  423.   var path = dirsvc.get('ProfD', Ci.nsIFile, bResult);
  424.   path.QueryInterface(Ci.nsIFile);
  425.   // the file where we store our pane's state
  426.   path.append('service-pane.rdf');
  427.  
  428.   var uri = IOSVC.newFileURI(path);
  429.   // FIXME: does this handle non-latin paths on non-linux?
  430.  
  431.   this._dataSource = RDFSVC.GetDataSourceBlocking(uri.spec);
  432.   this._dataSourceWrapped = new dsTranslator(this._dataSource, this);
  433.   this._dsSaver = new dsSaver(this._dataSource, 30*1000); // try to save every thirty seconds
  434.  
  435.   // the root of the tree
  436.   this._root = this.getNode('SB:Root');
  437.   if (!this._root) {
  438.     // looks like we need to bootstrap the tree
  439.     // create the root node
  440.     this._dataSource.Assert(RDFSVC.GetResource('SB:Root'),
  441.                 RDFSVC.GetResource(SP+'Hidden'),
  442.                 RDFSVC.GetLiteral('true'), true);
  443.     // make it a sequence
  444.     RDFCU.MakeSeq(this._dataSource, RDFSVC.GetResource('SB:Root'));
  445.     this._root = this.getNode('SB:Root');
  446.     this._root.hidden = false;
  447.   }
  448.  
  449.   // okay, lets get all the keys
  450.   this._modules = [];
  451.   var module_keys = [];
  452.   var catMgr = Cc["@mozilla.org/categorymanager;1"]
  453.       .getService(Ci.nsICategoryManager);
  454.   var e = catMgr.enumerateCategory('service-pane');
  455.   while (e.hasMoreElements()) {
  456.     var key = e.getNext().QueryInterface(Ci.nsISupportsCString);
  457.     module_keys.push(key);
  458.   }
  459.  
  460.   // let's sort the list
  461.   module_keys.sort()
  462.  
  463.   // and lets create and init the modules in order
  464.   for(var i=0; i<module_keys.length; i++) {
  465.     var key = module_keys[i];
  466.     var contractid = catMgr.getCategoryEntry('service-pane', key);
  467.     DEBUG ('trying to load contractid: '+contractid);
  468.     DEBUG ('Cc[contractid]: '+Cc[contractid]);
  469.     try {
  470.       var module = Cc[contractid].getService(Ci.sbIServicePaneModule);
  471.       module.servicePaneInit(this);
  472.       this._modules.push(module);
  473.     } catch (e) {
  474.       DEBUG('ERROR CREATING SERVICE PANE MODULE: '+e);
  475.     }
  476.   }
  477.  
  478.   // ensure we have a birdhouse node
  479.   // this is kind of a hack
  480.   var birdhouse = this.getNode('http://birdhouse.songbirdnest.com/');
  481.   if (!birdhouse) {
  482.     birdhouse = this.addNode('http://birdhouse.songbirdnest.com/',
  483.       this._root, false);
  484.   }
  485.   birdhouse.url = 'http://birdhouse.songbirdnest.com/';
  486.   birdhouse.name = '&servicesource.welcome';
  487.   birdhouse.hidden = false;
  488.   birdhouse.editable = false;
  489.   birdhouse.properties = 'birdhouse';
  490.   birdhouse.setAttributeNS(SP, 'Weight', -5);
  491.  
  492.   // HACK ALERT
  493.   // now, let's sort the top-level nodes by their weight.
  494.   // this idea of node weight is stolen from Drupal menus
  495.   var node = this._root.firstChild;
  496.   while (node) {
  497.     var value = parseInt(node.getAttributeNS(SP, 'Weight'));
  498.     while (node.previousSibling) {
  499.       var prev_value =
  500.           parseInt(node.previousSibling.getAttributeNS(SP, 'Weight'));
  501.       if (prev_value > value) {
  502.         this._root.insertBefore(node, node.previousSibling);
  503.       } else {
  504.         break;
  505.       }
  506.     }
  507.  
  508.     node = node.nextSibling;
  509.   }
  510.  
  511.   DEBUG('ServicePaneService.init() ends');
  512. }
  513.  
  514. ServicePaneService.prototype.shutdown = function ServicePaneService_shutdown() {
  515.   DEBUG('ServicePaneService.shutdown() begins');
  516.  
  517.   if (!this._initialized) {
  518.     // if we werent initialized, there's no need to shutdown
  519.     return;
  520.   }
  521.  
  522.   // Clear our array of service pane providers.  This is needed to break a
  523.   // reference cycle between the provider and this service
  524.   for (let i = 0; i < this._modules.length; i++) {
  525.     try {
  526.       this._modules[i].shutdown();
  527.     }
  528.     catch (e) {
  529.       // Ignore errors
  530.       Components.utils.reportError(e);
  531.     }
  532.     this._modules[i] = null;
  533.   }
  534.  
  535.   this._dataSource = null;
  536.   this._dataSourceWrapped = null;
  537.   this._root = null;
  538.   this._dsSaver = null;
  539.  
  540.   DEBUG('ServicePaneService.shutdown() ends');
  541. }
  542.  
  543. // for sbIServicePaneService.dataSource
  544. ServicePaneService.prototype.__defineGetter__('dataSource', function () {
  545.   if (!this._initialized) { this.init(); }
  546.   return this._dataSourceWrapped;
  547. });
  548.  
  549. // for sbIServicePaneService.root
  550. ServicePaneService.prototype.__defineGetter__('root', function () {
  551.   if (!this._initialized) { this.init(); }
  552.   return this._root;
  553. });
  554.  
  555. // FIXME: implement nsIClassInfo
  556. ServicePaneService.prototype.QueryInterface =
  557. function ServicePaneService_QueryInterface(iid) {
  558.   if (!iid.equals(Ci.sbIServicePaneService) &&
  559.     !iid.equals(Ci.nsIObserver) &&
  560.     !iid.equals(Ci.nsISupports)) {
  561.     throw Components.results.NS_ERROR_NO_INTERFACE;
  562.   }
  563.   return this;
  564. }
  565. ServicePaneService.prototype.addNode =
  566. function ServicePaneService_addNode(aId, aParent, aContainer) {
  567.   if (!this._initialized) { this.init(); }
  568.   DEBUG ('ServicePaneService.addNode('+aId+','+aParent+')');
  569.  
  570.   /* ensure the parent is supplied */
  571.   if (aParent == null) {
  572.     throw Ce('you need to supply a parent to addNode');
  573.   }
  574.  
  575.   var resource;
  576.   if (aId != null) {
  577.     resource = RDFSVC.GetResource(aId);
  578.     /* ensure the node doesn't already exist */
  579.     if (this._dataSource.GetTargets(resource,
  580.         RDFSVC.GetResource(SP+'Hidden'), true).hasMoreElements()) {
  581.       /* the node has a "hidden" attribute so it must exist */
  582.       return null;
  583.     }
  584.   } else {
  585.     /* no id supplied - make it anonymous */
  586.     resource = RDFSVC.GetAnonymousResource();
  587.   }
  588.  
  589.   /* create an arc making the node hidden, thus creating the object in RDF */
  590.   this._dataSource.Assert(resource,
  591.               RDFSVC.GetResource(SP+'Hidden'),
  592.               RDFSVC.GetLiteral('true'), true);
  593.   if (aContainer) {
  594.     RDFCU.MakeSeq(this._dataSource, resource);
  595.   }
  596.  
  597.   /* create the javascript proxy object */
  598.   var node = new ServicePaneNode(this._dataSource, resource);
  599.   DEBUG ('ServicePaneService.addNode: node.hidden='+node.hidden);
  600.  
  601.   /* add the node to the parent */
  602.   aParent.appendChild(node)
  603.  
  604.   /* if the node is a container, make it open */
  605.   if (aContainer) {
  606.     node.isOpen = true;
  607.   }
  608.  
  609.   // by default nothing is editable
  610.   node.editable = false;
  611.   return node;
  612. }
  613. ServicePaneService.prototype.removeNode =
  614. function ServicePaneService_removeNode(aNode) {
  615.   if (!this._initialized) { this.init(); }
  616.   /* ensure the node is supplied*/
  617.   if (aNode == null) {
  618.     throw Ce('you need to supply a node to removeNode');
  619.   }
  620.  
  621.   if (aNode.id == this._root.id) {
  622.     throw Ce("you can't remove the root node");
  623.   }
  624.  
  625.   if(aNode.parentNode) {
  626.     // remove the node from it's parent
  627.     aNode.parentNode.removeChild(aNode);
  628.   } else {
  629.     // or if it's an orphan call the internal function to clear it out
  630.     aNode.clearNode();
  631.   }
  632. }
  633. ServicePaneService.prototype.getNode =
  634. function ServicePaneService_getNode(aId) {
  635.   if (!this._initialized) { this.init(); }
  636.   var resource = RDFSVC.GetResource(aId);
  637.   /* ensure the node already exists */
  638.   if (!this._dataSource.GetTargets(resource,
  639.       RDFSVC.GetResource(SP+'Hidden'), true).hasMoreElements()) {
  640.     /* the node has a "hidden" attribute so it must exist */
  641.     return null;
  642.   }
  643.  
  644.   return new ServicePaneNode(this._dataSource, resource);
  645. }
  646.  
  647. ServicePaneService.prototype.getNodeForURL =
  648. function ServicePaneService_getNodeForURL(aURL) {
  649.   if (!this._initialized) { this.init(); }
  650.  
  651.   // HACK:  See BUG 4662 for more information.
  652.   //
  653.   // Library searches can cause the library to load as
  654.   // sbLibraryPage.xul?GUID&search=blah.  This extra
  655.   // search param breaks the library node highlighting.
  656.   // For now we fix this by chopping off the param.
  657.   if (aURL.indexOf("chrome://songbird/content/xul/sbLibraryPage.xul") != -1) {
  658.     var paramStart = aURL.indexOf("&");
  659.     if (paramStart != -1) {
  660.       aURL = aURL.substring(0,paramStart);
  661.     }
  662.   }
  663.  
  664.  
  665.   var ncURL = RDFSVC.GetResource(NC+"URL");
  666.   var url = RDFSVC.GetLiteral(aURL);
  667.   var target = this._dataSource.GetSource(ncURL, url, true);
  668.   return (target) ? this.getNode(target.Value) : null;
  669. }
  670.  
  671. ServicePaneService.prototype.save =
  672. function ServicePaneService_save() {
  673.   /* FIXME: this function should go away */
  674. }
  675.  
  676. ServicePaneService.prototype.fillContextMenu =
  677. function ServicePaneService_fillContextMenu(aId, aContextMenu, aParentWindow) {
  678.   if (!this._initialized) { this.init(); }
  679.   var node = aId ? this.getNode(aId) : null;
  680.   for (var i=0; i<this._modules.length; i++) {
  681.     try {
  682.       this._modules[i].fillContextMenu(node, aContextMenu, aParentWindow);
  683.     } catch (ex) {
  684.       if (gPrefs.getPrefType('javascript.options.showInConsole') &&
  685.           gPrefs.getBoolPref('javascript.options.showInConsole')) {
  686.         Components.utils.reportError(ex);
  687.       }
  688.     }
  689.   }
  690. }
  691.  
  692. ServicePaneService.prototype.fillNewItemMenu =
  693. function ServicePaneService_fillNewItemMenu(aId, aContextMenu, aParentWindow) {
  694.   if (!this._initialized) { this.init(); }
  695.   var node = aId ? this.getNode(aId) : null;
  696.   for (var i=0; i<this._modules.length; i++) {
  697.     try {
  698.       this._modules[i].fillNewItemMenu(node, aContextMenu, aParentWindow);
  699.     } catch (ex) {
  700.       if (gPrefs.getPrefType('javascript.options.showInConsole') &&
  701.           gPrefs.getBoolPref('javascript.options.showInConsole')) {
  702.         Components.utils.reportError(ex);
  703.       }
  704.     }
  705.   }
  706. }
  707.  
  708. ServicePaneService.prototype.onSelectionChanged =
  709. function ServicePaneService_onSelectionChanged(aId, aContainer, aParentWindow) {
  710.   if (!this._initialized) { this.init(); }
  711.   var node = aId ? this.getNode(aId) : null;
  712.   for (var i=0; i<this._modules.length; i++) {
  713.     try {
  714.       this._modules[i].onSelectionChanged(node, aContainer, aParentWindow);
  715.     } catch (ex) {
  716.       if (gPrefs.getPrefType('javascript.options.showInConsole') &&
  717.           gPrefs.getBoolPref('javascript.options.showInConsole')) {
  718.         Components.utils.reportError(ex);
  719.       }
  720.     }
  721.   }
  722. }
  723.  
  724. ServicePaneService.prototype._canDropReorder =
  725. function ServicePaneService__canDropReorder(aNode, aDragSession, aOrientation) {
  726.   if (!this._initialized) { this.init(); }
  727.   // see if we can handle the drag and drop based on node properties
  728.   var types = [];
  729.   if (aOrientation == 0) {
  730.     // drop in
  731.     if (!aNode.isContainer) {
  732.       // not a container - don't even try
  733.       return null;
  734.     }
  735.     if (aNode.dndAcceptIn) {
  736.       types = aNode.dndAcceptIn.split(',');
  737.     }
  738.   } else {
  739.     // drop near
  740.     if (aNode.dndAcceptNear) {
  741.       types = aNode.dndAcceptNear.split(',');
  742.     }
  743.   }
  744.   for (var i=0; i<types.length; i++) {
  745.     if (aDragSession.isDataFlavorSupported(types[i])) {
  746.       return types[i];
  747.     }
  748.   }
  749.   return null;
  750. }
  751. ServicePaneService.prototype.canDrop =
  752. function ServicePaneService_canDrop(aId, aDragSession, aOrientation) {
  753.   if (!this._initialized) { this.init(); }
  754.   var node = this.getNode(aId);
  755.   if (!node) {
  756.     return false;
  757.   }
  758.  
  759.   // see if we can handle the drag and drop based on node properties
  760.   if (this._canDropReorder(node, aDragSession, aOrientation)) {
  761.     return true;
  762.   }
  763.  
  764.   // let the module that owns this node handle this
  765.   if (node.contractid && Cc[node.contractid]) {
  766.     var module = Cc[node.contractid].getService(Ci.sbIServicePaneModule);
  767.     if (module) {
  768.       return module.canDrop(node, aDragSession, aOrientation);
  769.     }
  770.   }
  771.   return false;
  772. }
  773. ServicePaneService.prototype.onDrop =
  774. function ServicePaneService_onDrop(aId, aDragSession, aOrientation) {
  775.   if (!this._initialized) { this.init(); }
  776.   var node = this.getNode(aId);
  777.   if (!node) {
  778.     return;
  779.   }
  780.   // see if this is a reorder we can handle based on node properties
  781.   var type = this._canDropReorder(node, aDragSession, aOrientation);
  782.   if (type) {
  783.     // we're in business
  784.  
  785.     // do the dance to get our data out of the dnd system
  786.     // create an nsITransferable
  787.     var transferable = Components.classes["@mozilla.org/widget/transferable;1"].
  788.         createInstance(Components.interfaces.nsITransferable);
  789.     // specify what kind of data we want it to contain
  790.     transferable.addDataFlavor(type);
  791.     // ask the drag session to fill the transferable with that data
  792.     aDragSession.getData(transferable, 0);
  793.     // get the data from the transferable
  794.     var data = {};
  795.     var dataLength = {};
  796.     transferable.getTransferData(type, data, dataLength);
  797.     // it's always a string. always.
  798.     data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
  799.     data = data.toString();
  800.  
  801.     // for drag and drop reordering the data is just the servicepane uri
  802.     var droppedNode = this.getNode(data);
  803.  
  804.     // fail if we can't get the node
  805.     if (!droppedNode) {
  806.       return;
  807.     }
  808.  
  809.     if (aOrientation == 0) {
  810.       // drop into
  811.       // we should append the new node to the current node
  812.       node.appendChild(droppedNode);
  813.     } else if (aOrientation > 0) {
  814.       // drop after
  815.       if (node.nextSibling) {
  816.         // not the last node
  817.         if (droppedNode.id == node.nextSibling.id) {
  818.           return; // can't insert after ourselves
  819.         }
  820.         node.parentNode.insertBefore(droppedNode, node.nextSibling);
  821.       } else {
  822.         // the last node
  823.         node.parentNode.appendChild(droppedNode);
  824.       }
  825.     } else {
  826.       // drop before
  827.       if (droppedNode.id == node.id) {
  828.         return; // can't insert before ourselves
  829.       }
  830.       node.parentNode.insertBefore(droppedNode, node);
  831.     }
  832.     return;
  833.   }
  834.  
  835.   // or let the module that owns this node handle it
  836.   if (node.contractid) {
  837.     var module = Cc[node.contractid].getService(Ci.sbIServicePaneModule);
  838.     if (module) {
  839.       module.onDrop(node, aDragSession, aOrientation);
  840.     }
  841.   }
  842. }
  843. ServicePaneService.prototype.onDragGesture =
  844. function ServicePaneService_onDragGesture(aId, aTransferable) {
  845.   if (!this._initialized) { this.init(); }
  846.   DEBUG('onDragGesture('+aId+', '+aTransferable+')');
  847.   var node = this.getNode(aId);
  848.   if (!node) {
  849.     return false;
  850.   }
  851.  
  852.   var success = false;
  853.  
  854.   // create a transferable
  855.   var transferable = Components.classes["@mozilla.org/widget/transferable;1"].
  856.     createInstance(Components.interfaces.nsITransferable);
  857.  
  858.   // get drag types from the node data
  859.   if (node.dndDragTypes) {
  860.     var types = node.dndDragTypes.split(',');
  861.     for (var i=0; i<types.length; i++) {
  862.       var type = types[i];
  863.       transferable.addDataFlavor(type);
  864.       var text = Components.classes["@mozilla.org/supports-string;1"].
  865.          createInstance(Components.interfaces.nsISupportsString);
  866.       text.data = node.id;
  867.       // double the length - it's unicode - this is stupid
  868.       transferable.setTransferData(type, text, text.data.length*2);
  869.       success = true;
  870.     }
  871.   }
  872.  
  873.   if (node.contractid && Cc[node.contractid]) {
  874.     var module = Cc[node.contractid].getService(Ci.sbIServicePaneModule);
  875.     if (module) {
  876.       if (module.onDragGesture(node, transferable)) {
  877.         if (!success) {
  878.           success = true;
  879.         }
  880.       }
  881.     }
  882.   }
  883.  
  884.   if (success) {
  885.     aTransferable.value = transferable;
  886.   }
  887.  
  888.   DEBUG(' success='+success);
  889.  
  890.   return success;
  891. }
  892.  
  893. /**
  894.  * Called when a node is renamed by the user.
  895.  * Delegates to the module that owns the given node.
  896.  */
  897. ServicePaneService.prototype.onRename =
  898. function ServicePaneService_onRename(aID, aNewName) {
  899.   if (!this._initialized) { this.init(); }
  900.   var node = this.getNode(aID);
  901.   if (!node || !node.editable) {
  902.     return;
  903.   }
  904.  
  905.   // Pass the message on to the node owner
  906.   if (node.contractid) {
  907.     var module = Cc[node.contractid].getService(Ci.sbIServicePaneModule);
  908.     if (module) {
  909.       module.onRename(node, aNewName);
  910.     }
  911.   }
  912. }
  913.  
  914. /* this is a wrapper for the nsIRDFDataSource that will translate some of the
  915.   properties via stringbundles */
  916. function dsTranslator(inner, sps) {
  917.   this.inner = inner;
  918.   this.sps = sps;
  919.   this.properties = {};
  920.   this.properties[NC+'Name'] = true;
  921.   this.stringBundleService = Cc['@mozilla.org/intl/stringbundle;1']
  922.       .getService(Ci.nsIStringBundleService);
  923. }
  924. dsTranslator.prototype = {
  925.   // impl methods
  926.  
  927.   // Try to translate a key based on a string bundle uri. Either return an
  928.   // nsIRDFLiteral on success or null on failure.
  929.   translateKey: function translateKey(stringbundleuri, key) {
  930.     var bundle = this.stringBundleService.createBundle(stringbundleuri);
  931.     if (!bundle) { return null; }
  932.     var message = null;
  933.     try {
  934.       message = bundle.GetStringFromName(key);
  935.     } catch (e) { }
  936.     if (!message) { return null; }
  937.     return RDFSVC.GetLiteral(message);
  938.   },
  939.  
  940.   // Try to translate an RDF triple. Either return either the original or
  941.   // the translated target
  942.   translateTriple: function translateTriple(aSource, aProperty, aTarget) {
  943.     // if there's no target, fail
  944.     if (!aTarget) { return null; }
  945.  
  946.     // if the target is not an nsIRDFLiteral, return it
  947.     if (!(aTarget instanceof Ci.nsIRDFLiteral)) { return aTarget; }
  948.  
  949.     // if the target does not begin with "&", return it
  950.     if (aTarget.Value.length < 1 ||
  951.         aTarget.Value[0] != '&') {
  952.       return aTarget;
  953.     }
  954.  
  955.     // the key is the rest of aTarget.Value
  956.     var key = aTarget.Value.substring(1);
  957.  
  958.     // we need to find the string bundle we look in these places in this
  959.     // order:
  960.     //   a property on the node (http://songbirdnest.com/rdf/servicepane#stringbundle)
  961.     //   the .stringbundle attribute on the associated module service
  962.     //   the default stringbundle on the service pane service
  963.  
  964.     // FIXME: after testing wrap each of these steps in try/catch
  965.  
  966.     var node = this.sps.getNode(aSource.Value);
  967.  
  968.     // the node's stringbundle
  969.     if (node && node.stringbundle) {
  970.       var translated = this.translateKey(node.stringbundle, key);
  971.       if (translated) {
  972.         return translated;
  973.       }
  974.     }
  975.  
  976.     // the module's stringbundle
  977.     if (node && node.contractid && Cc[node.contractid]) {
  978.       var m = Cc[node.contractid].getService(Ci.sbIServicePaneModule);
  979.       if (m && m.stringbundle) {
  980.         var translated = this.translateKey(m.stringbundle, key);
  981.         if (translated) {
  982.           return translated
  983.         }
  984.       }
  985.     }
  986.  
  987.     // the app's default stringbundle
  988.     var translated = this.translateKey(STRINGBUNDLE, key)
  989.     if (translated) {
  990.       return translated;
  991.     }
  992.  
  993.     // couldn't translate it - let's just return the raw target
  994.     return aTarget;
  995.   },
  996.  
  997.   // nsISupports
  998.   QueryInterface: function QueryInterface(iid) {
  999.     return this.inner.QueryInterface(iid);
  1000.   },
  1001.  
  1002.   // nsIRDFDataSource
  1003.   get URI() { return this.inner.URI; },
  1004.   GetSource: function GetSource(a,b,c) { return this.inner.GetSource(a,b,c); },
  1005.   GetSources: function GetSources(a,b,c) { return this.inner.GetSources(a,b,c); },
  1006.   GetTarget: function GetTarget(aSource, aProperty, aTruthValue) {
  1007.     var target = this.inner.GetTarget(aSource, aProperty, aTruthValue);
  1008.  
  1009.     // is this property translatable
  1010.     if (this.properties[aProperty.Value]) {
  1011.       return this.translateTriple(aSource, aProperty, target);
  1012.     } else {
  1013.       return target;
  1014.     }
  1015.   },
  1016.   // FIXME do we need to translate GetTargets too?
  1017.   GetTargets: function GetTargets(aSource, aProperty, aTruthValue) {
  1018.     var targets = this.inner.GetTargets(aSource, aProperty, aTruthValue);
  1019.  
  1020.     // is this property translatable?
  1021.     if (this.properties[aProperty.Value]) {
  1022.       // we need to create a proxy enumerator object
  1023.       var self = this;
  1024.       var enumerator = {
  1025.         hasMoreElements: function hasMoreElements() {
  1026.           return targets.hasMoreElements();
  1027.         },
  1028.         getNext: function getNext() {
  1029.           return self.translateTriple(aSource, aProperty,
  1030.               targets.getNext());
  1031.         },
  1032.         QueryInterface: function QueryInterface(iid) {
  1033.           return targets.QueryInterface(iid);
  1034.         }
  1035.       };
  1036.       return enumerator
  1037.     } else {
  1038.       return targets;
  1039.     }
  1040.   },
  1041.   Assert: function Assert(a,b,c,d) { this.inner.Assert(a,b,c,d); },
  1042.   Unassert: function Unassert(a,b,c) { this.inner.Unassert(a,b,c); },
  1043.   Change: function Change(a,b,c,d) { this.inner.Change(a,b,c,d); },
  1044.   Move: function Move(a,b,c,d) { this.inner.Move(a,b,c,d); },
  1045.   HasAssertion: function HasAssertion(a,b,c,d) { return this.inner.HasAssertion(a,b,c,d); },
  1046.   AddObserver: function AddObserver(a) { this.inner.AddObserver(a); },
  1047.   RemoveObserver: function RemoveObserver(a) { this.inner.RemoveObserver(a); },
  1048.   ArcLabelsIn: function ArcLabelsIn(a) { return this.inner.ArcLabelsIn(a); },
  1049.   ArcLabelsOut: function ArcLabelsOut(a) { return this.inner.ArcLabelsOut(a); },
  1050.   GetAllResources: function GetAllResources() { return this.inner.GetAllResourvces(); },
  1051.   IsCommandEnabled: function IsCommandEnabled(a,b,c) { return this.inner.IsCommandEnabled(a,b,c); },
  1052.   DoCommand: function DoCommand(a,b,c) { this.inner.DoCommand(a,b,c); },
  1053.   IsCommandEnabled: function IsCommandEnabled(a,b,c) { return this.inner.IsCommandEnabled(a,b,c); },
  1054.   GetAllCmds: function GetAllCmds() { return this.inner.GetAllCmds(); },
  1055.   hasArcIn: function hasArcIn(a,b) { this.inner.hasArcIn(a,b); },
  1056.   hasArcOut: function hasArcOut(a,b) { this.inner.hasArcOut(a,b); },
  1057.   beginUpdateBatch: function beginUpdateBatch() { this.inner.beginUpdateBatch(); },
  1058.   endUpdateBatch: function endUpdateBatch() { this.inner.endUpdateBatch(); },
  1059.  
  1060.   // nsIRDFRemoteDataSource
  1061.   get loaded() { return this.inner.loaded; },
  1062.   Init: function Init(a) { this.inner.Init(a); },
  1063.   Refresh: function Refresh(a) { this.inner.Refresh(a); },
  1064.   Flush: function Flush() { this.inner.Flush(); },
  1065.   FlushTo: function FlushTo(a) { this.inner.FlushTo(a); }
  1066. };
  1067.  
  1068.  
  1069. /* an RDF observer that handles automatic saving of RDF state */
  1070. function dsSaver(ds, frequency) {
  1071.   this.ds = ds.QueryInterface(Ci.nsIRDFDataSource);
  1072.   this.ds.QueryInterface(Ci.nsIRDFRemoteDataSource);
  1073.  
  1074.   this.dirty = false; // has the DS changed since it was last saved?
  1075.  
  1076.   this.timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
  1077.   this.timer.init(this, frequency, Ci.nsITimer.TYPE_REPEATING_SLACK);
  1078.  
  1079.   Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService)
  1080.       .addObserver(this, 'quit-application', false);
  1081.  
  1082.   this.ds.AddObserver(this);
  1083. }
  1084. dsSaver.prototype = {
  1085.   save: function dsSaver_save() {
  1086.     if (this.dirty) {
  1087.       this.ds.Flush();
  1088.       this.dirty = false;
  1089.     }
  1090.   },
  1091.   /* nsISupports */
  1092.   QueryInterface: function dsSaver_QueryInterface(iid) {
  1093.     if (!iid.equals(Ci.nsIRDFObserver) &&
  1094.         !iid.equals(Ci.nsIObserver) &&
  1095.         !iid.equals(Ci.nsISupports)) {
  1096.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1097.     }
  1098.     return this;
  1099.   },
  1100.  
  1101.   /* nsIObserver */
  1102.   observe: function dsSaver_observe(subject, topic, data) {
  1103.     switch (topic) {
  1104.       case 'timer-callback':
  1105.         if (subject == this.timer) {
  1106.           this.save();
  1107.         }
  1108.         break;
  1109.       case 'quit-application':
  1110.         this.save();
  1111.         Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService)
  1112.           .removeObserver(this, 'quit-application');
  1113.         this.ds.RemoveObserver(this);
  1114.         this.timer.cancel();
  1115.         this.timer = null;
  1116.         break;
  1117.       default:
  1118.     }
  1119.   },
  1120.  
  1121.   /* nsIRDFObserver */
  1122.   onAssert: function dsSaver_onAssert(a, b, c, d) {
  1123.     this.dirty = true;
  1124.   },
  1125.   onUnassert: function dsSaver_onUnassert(a, b, c, d) {
  1126.     this.dirty = true;
  1127.   },
  1128.   onChange: function dsSaver_onChange(a, b, c, d, e) {
  1129.     this.dirty = true;
  1130.   },
  1131.   onMove: function dsSaver_onMove(a, b, c, d, e) {
  1132.     this.dirty = true;
  1133.   },
  1134.   onBeginUpdateBatch: function dsSaver_onBeginUpdateBatch(a) { },
  1135.   onEndUpdateBatch: function dsSaver_onEndUpdateBatch(a) { }
  1136. }
  1137.  
  1138. /**
  1139.  * /brief XPCOM initialization code
  1140.  */
  1141. function makeGetModule(CONSTRUCTOR, CID, CLASSNAME, CONTRACTID, CATEGORIES) {
  1142.   return function (comMgr, fileSpec) {
  1143.     return {
  1144.       registerSelf : function (compMgr, fileSpec, location, type) {
  1145.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1146.         compMgr.registerFactoryLocation(CID,
  1147.                         CLASSNAME,
  1148.                         CONTRACTID,
  1149.                         fileSpec,
  1150.                         location,
  1151.                         type);
  1152.         if (CATEGORIES && CATEGORIES.length) {
  1153.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  1154.               .getService(Ci.nsICategoryManager);
  1155.           for (var i=0; i<CATEGORIES.length; i++) {
  1156.             var e = CATEGORIES[i];
  1157.             catman.addCategoryEntry(e.category, e.entry, e.value,
  1158.               true, true);
  1159.           }
  1160.         }
  1161.       },
  1162.  
  1163.       getClassObject : function (compMgr, cid, iid) {
  1164.         if (!cid.equals(CID)) {
  1165.           throw Cr.NS_ERROR_NO_INTERFACE;
  1166.         }
  1167.  
  1168.         if (!iid.equals(Ci.nsIFactory)) {
  1169.           throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1170.         }
  1171.  
  1172.         return this._factory;
  1173.       },
  1174.  
  1175.       _factory : {
  1176.         createInstance : function (outer, iid) {
  1177.           if (outer != null) {
  1178.             throw Cr.NS_ERROR_NO_AGGREGATION;
  1179.           }
  1180.           return (new CONSTRUCTOR()).QueryInterface(iid);
  1181.         }
  1182.       },
  1183.  
  1184.       unregisterSelf : function (compMgr, location, type) {
  1185.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1186.         compMgr.unregisterFactoryLocation(CID, location);
  1187.         if (CATEGORIES && CATEGORIES.length) {
  1188.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  1189.               .getService(Ci.nsICategoryManager);
  1190.           for (var i=0; i<CATEGORIES.length; i++) {
  1191.             var e = CATEGORIES[i];
  1192.             catman.deleteCategoryEntry(e.category, e.entry, true);
  1193.           }
  1194.         }
  1195.       },
  1196.  
  1197.       canUnload : function (compMgr) {
  1198.         return true;
  1199.       },
  1200.  
  1201.       QueryInterface : function (iid) {
  1202.         if ( !iid.equals(Ci.nsIModule) ||
  1203.              !iid.equals(Ci.nsISupports) )
  1204.           throw Cr.NS_ERROR_NO_INTERFACE;
  1205.         return this;
  1206.       }
  1207.  
  1208.     };
  1209.   }
  1210. }
  1211.  
  1212. var NSGetModule = makeGetModule (
  1213.   ServicePaneService,
  1214.   Components.ID("{eb5c665a-bfe2-49f1-a747-cd3554e55606}"),
  1215.   "Songbird Service Pane Service",
  1216.   "@songbirdnest.com/servicepane/service;1",
  1217.   [{
  1218.     category: 'app-startup',
  1219.     entry: 'service-pane-service',
  1220.     value: 'service,@songbirdnest.com/servicepane/service;1'
  1221.   }]);
  1222.