<?xml version="1.0" ?> 
<!DOCTYPE window [
  <!ENTITY % bookmarksDTD SYSTEM "chrome://communicator/locale/bookmarks/bookmarks.dtd" >
  %bookmarksDTD;
]>

<bindings id="bookmarksBindings" 
          xmlns="http://www.mozilla.org/xbl" 
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 
          xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">

  <binding id="bookmarksBase">
    <implementation>
      <!-- RDF Namespace URIs -->
      <field name="RDF_NS">"http://www.w3.org/1999/02/22-rdf-syntax-ns#"</field>
      <field name="NC_NS">"http://home.netscape.com/NC-rdf#"</field>
      <field name="NC_NS_CMD">this.NC_NS + "command?cmd=";</field>
      <property name="rdfContainer">
        <getter><![CDATA[
          const kRDFCContractID = "@mozilla.org/rdf/container;1";
          const kRDFCIID = Components.interfaces.nsIRDFContainer;
          return Components.classes[kRDFCContractID].getService(kRDFCIID);
        ]]></getter>
      </property>
      <field name="_rdf">null</field>
      <property name="rdf">
        <getter><![CDATA[
          if (!this._rdf) {
            const kRDFContractID = "@mozilla.org/rdf/rdf-service;1";
            const kRDFIID = Components.interfaces.nsIRDFService;
            this._rdf = Components.classes[kRDFContractID].getService(kRDFIID);
          }
          return this._rdf;
        ]]></getter>
      </property>
      <field name="_bundle">null</field>
      <field name="_bookmarksDS">null</field>
      <property name="bookmarksDS">
        <getter><![CDATA[
          if (!this._bookmarksDS)
            this._bookmarksDS = this.rdf.GetDataSource("rdf:bookmarks");
          return this._bookmarksDS;
        ]]></getter>
      </property>
      <method name="resolveType">
        <parameter name="aResource"/>
        <body><![CDATA[
          try {
            aResource.QueryInterface(Components.interfaces.nsIRDFResource);
          }
          catch(ex) {
            // Not actually a resource, assume resource URI
            aResource = this.rdf.GetResource(aResource);
          }
          var res = aResource;
          var typeArc = this.rdf.GetResource(this.RDF_NS + "type");
          var type = this.db.GetTarget(res, typeArc, true);
          try {
            return type.QueryInterface(Components.interfaces.nsIRDFResource).Value;
          }
          catch (e) {
            try { 
              return type.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
            }
            catch (e) {
              return null;
            }
          }
        ]]></body>
      </method>
      <method name="flushBMDatasource">
        <body><![CDATA[
          var remoteDS = this.bookmarksDS.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
          remoteDS.Flush();
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="bookmarks-outliner"
           extends="chrome://communicator/content/bookmarks/bookmarks.xml#bookmarksBase">
    <implementation>
      <constructor><![CDATA[
        var bookmarksSvc = Components.classes["@mozilla.org/browser/bookmarks-service;1"].getService(Components.interfaces.nsIBookmarksService);
        // This function only reads in the bookmarks from disk if they have not already been read.
        bookmarksSvc.ReadBookmarks();
        
        // We implement nsIController
        this.outliner.controllers.appendController(this.controller);
        var olb = document.getAnonymousElementByAttribute(this, "anonid", "bookmarks-outliner");
        olb = olb.builder.QueryInterface(Components.interfaces.nsIXULOutlinerBuilder);
        olb.addObserver(this.builderObserver);
        // need to create string bundle manually instead of using <xul:stringbundle/>
        // see bug 63370 for details
        var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
                                      .getService(Components.interfaces.nsILocaleService);
        var stringBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService);
        var bundleURL = "chrome://communicator/locale/bookmarks/bookmark.properties";
        this._bundle = stringBundleService.createBundle(bundleURL, localeService.GetApplicationLocale());
      ]]></constructor>
      <destructor><![CDATA[
        this.outlinerBuilder.removeObserver(this.builderObserver);
        this.outliner.controllers.removeController(this.controller);
      ]]></destructor>

      <property name="outlinerBoxObject">
        <getter><![CDATA[
          return this.outliner.boxObject.QueryInterface(Components.interfaces.nsIOutlinerBoxObject);
        ]]></getter>
      </property>

      <property name="outlinerBuilder">
        <getter><![CDATA[
          return this.outliner.builder.QueryInterface(Components.interfaces.nsIXULOutlinerBuilder);
        ]]></getter>
      </property>

      <property name="outliner">
        <getter><![CDATA[
          return document.getAnonymousElementByAttribute(this, "anonid", "bookmarks-outliner");
        ]]></getter>
      </property>

      <property name="currentIndex">
        <getter><![CDATA[
          return this.outlinerBoxObject.selection.currentIndex;
        ]]></getter>
      </property>

      <property name="currentRes">
        <getter><![CDATA[
          return this.outlinerBuilder.getResourceAtIndex(this.currentIndex);
        ]]></getter>
      </property>

      <property name="parentRes">
        <getter><![CDATA[
          const currIndex = this.currentIndex;

          if (currIndex == -1)
            return this.rdf.GetResource("NC:BookmarksRoot");

          var parentIndex = this.outlinerBoxObject.view.getParentIndex(currIndex);
          if (parentIndex != -1)
            return this.outlinerBuilder.getResourceAtIndex(parentIndex)
          return this.rdf.GetResource("NC:BookmarksRoot"); // assume its parent is the root
        ]]></getter>
      </property>

      <property name="firstSelectedIndex">
        <getter><![CDATA[
          var first = { };
          this.outlinerBoxObject.selection.getRangeAt(0, first, { });
          return first.value;
        ]]></getter>
      </property>

      <property name="lastSelectedIndex">
        <getter><![CDATA[
          var bo = this.outlinerBoxObject;
          var last = { };
          var rangeCount = bo.selection.getRangeCount();
          bo.selection.getRangeAt(rangeCount - 1, { }, last);
          return last.value;
        ]]></getter>
      </property>
      
      <property name="_browserURL">
        <getter><![CDATA[
          try {
            var prefs = Components.classes["@mozilla.org/preferences;1"];
            if (prefs) {
              prefs = prefs.getService();
              if (prefs)
                prefs = prefs.QueryInterface(Components.interfaces.nsIPref);
            }
            if (prefs) {
              var url = prefs.CopyCharPref("browser.chromeURL");
              if (url)
                return url;
            }
          } catch(e) {
          }
          return "chrome://navigator/content/navigator.xul";
        ]]></getter>
      </property>
      
      <field name="_type">null</field>
 
      <property name="type">
        <getter><![CDATA[
          if (!this._type) {
            var type = this.getAttribute("type");
            if (!type)
              type = "multi-column";
            this._type = type;
          }
          return this._type;
        ]]></getter>
      </property>
      
      <!-- Returns the row index of the best row at which to perform an operation
           relative to the current selection, e.g. creating a new bookmark 
           adjacent to the current selection. -->
      <method name="getNextRowIndex">
        <body><![CDATA[
          var bo = this.outlinerBoxObject;
          var rangeCount = bo.selection.getRangeCount();
          var currLevel, lastIndex = 0;
          if (!rangeCount) 
            currLevel = 0;
          else
            currLevel = bo.view.getLevel(this.firstSelectedIndex);

          // Walk the selection. If the level for the current item is less than
          // our running minimum, assume it is higher in the hierarchy than any
          // previous. In this case, or in the case with a selected item of same
          // hierarchy, set the lastIndex.
          // XXXben - erk this comment sort of sucks. A diagram might help. 
          for (var i = 0; i < rangeCount; ++i) {
            var rangeMin = { };
            var rangeMax = { };
            bo.selection.getRangeAt(i, rangeMin, rangeMax);
            for (var j = rangeMax.value; j >= rangeMin.value; --j) {
              var level = bo.view.getLevel(j);
              if (level <= currLevel) {
                currLevel = level;
                lastIndex = j;
              }
            } 
          }
          return lastIndex;
        ]]></body>
      </method>

      <method name="getTypeAtIndex">
        <parameter name="aIndex" />
        <body><![CDATA[
          try {
            return this.resolveType(this.outlinerBuilder.getResourceAtIndex(aIndex));
          }
          catch(ex) {
            return null;
          }
        ]]></body>
      </method>

      <!-- observer -->
      <field name="newFolderRDFObserver" readonly="true"><![CDATA[
      ({
        _newFolderURI: null,
        onAssert: function (aDS, aSource, aProperty, aValue)
        {
          try {
            var value = aValue.QueryInterface(Components.interfaces.nsIRDFResource);
            if (aDS.URI == "rdf:bookmarks" && aProperty.Value == this.RDF_NS + "type" &&
                value.Value == this.NC_NS + "Folder")
              this._newFolderURI = aSource.Value;
          }
          catch (e) {
            // Failures are OK, the value could be a literal instead of a resource.
          }
        },
        onUnassert: function (aDS, aSource, aProperty, aTarget) { },
        onChange: function (aDS, aSource, aProperty, aOldTarget, aNewTarget) { },
        onMove: function (aDS, aOldSource, aNewSource, aProperty, aTarget) { },
        beginUpdateBatch: function (aDS) { },
        endUpdateBatch: function (aDS) { }
      })
      ]]></field>

      <!-- observer -->
      <field name="DNDObserver" readonly="true"><![CDATA[
      ({
        mOuter: this,
        onDragStart: function (aEvent, aXferData, aDragAction)
        {
          if (this.mOuter.outliner.getAttribute("sortActive") == "true")
            throw Components.results.NS_OK; 
          aXferData.data = new TransferDataSet();
          var rangeCount = this.mOuter.outlinerBoxObject.selection.getRangeCount();
          for (var i = rangeCount - 1; i >= 0; --i) {
            var rangeMin = { };
            var rangeMax = { };
            this.mOuter.outlinerBoxObject.selection.getRangeAt(i, rangeMin, rangeMax);
            for (var j = rangeMax.value; j >= rangeMin.value; --j) {
              var currRes = this.mOuter.outlinerBuilder.getResourceAtIndex(j); 
              var parentIndex = this.mOuter.outlinerBoxObject.view.getParentIndex(j);
              var parentRes;
              if (parentIndex == -1)
                parentRes = this.mOuter.rdf.GetResource("NC:BookmarksRoot");
              else
                parentRes = this.mOuter.outlinerBuilder.getResourceAtIndex(parentIndex);
    
              var type = this.mOuter.db.GetTarget(currRes, this.mOuter.rdf.GetResource(this.mOuter.RDF_NS + "type"), true);
              type = type.QueryInterface(Components.interfaces.nsIRDFResource).Value;
              if (!type || (type != (this.mOuter.NC_NS + "BookmarkSeparator") && 
                  type != (this.mOuter.NC_NS + "Bookmark") && 
                  type != (this.mOuter.NC_NS + "Folder"))) 
                throw Components.results.NS_OK;
              var name = this.mOuter.db.GetTarget(currRes, this.mOuter.rdf.GetResource(this.mOuter.NC_NS + "Name"), true);        
              var data = new TransferData();
              if (name) {
                name = name.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
                data.addDataForFlavour("text/x-moz-url", currRes.Value + "\n" + name);
              }
              else {
                data.addDataForFlavour("text/x-moz-url", currRes.Value);
              }
              data.addDataForFlavour("moz/rdfitem", currRes.Value + "\n" + parentRes.Value);
              data.addDataForFlavour("text/unicode", currRes.Value);
              aXferData.data.push(data);
            }

           if (aEvent.ctrlKey) {
             const kDSIID = Components.interfaces.nsIDragService;
             aDragAction.action = kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_LINK;
           }
         }
       }
     })
     ]]></field>      
        
      <!-- nsIController -->
      <field name="controller" readonly="true"><![CDATA[
      ({
        mOuter: this,
        
        supportsCommand: function BMOLController_supportsCommand(aCommand) 
        {
          switch (aCommand) {
          case "cmd_bm_undo":
          case "cmd_bm_redo":
            return false;
          case "cmd_bm_cut":
          case "cmd_bm_copy":
          case "cmd_bm_paste":
          case "cmd_bm_delete":
          case "cmd_bm_selectAll":
          case "cmd_bm_open":
          case "cmd_bm_openinnewwindow":
          case "cmd_bm_openfolder":
          case "cmd_bm_newbookmark":
          case "cmd_bm_newfolder":
          case "cmd_bm_newseparator":
          case "cmd_bm_find":
          case "cmd_bm_properties":
          case "cmd_bm_rename":
          case "cmd_bm_setnewbookmarkfolder":
          case "cmd_bm_setpersonaltoolbarfolder":
          case "cmd_bm_setnewsearchfolder":
          case "cmd_bm_import":
          case "cmd_bm_export":
          case "cmd_bm_fileBookmark":
            return true;
          default:
            return false;
          }
        },
        
        isCommandEnabled: function BMOLController_isCommandEnabled(aCommand)
        {
          var bo = this.mOuter.outlinerBoxObject;
          var type;   // The bookmark's RDF:type arc
          
          switch (aCommand) {
          case "cmd_bm_undo":
          case "cmd_bm_redo":
            return false;
          case "cmd_bm_openfolder":
            // 'Expand' is only available if one item is selected and that item is a
            // container item.
            return bo.view.rowCount && bo.selection.count == 1 && bo.view.isContainer(this.mOuter.firstSelectedIndex);
          case "cmd_bm_open":
            return this.mOuter.getTypeAtIndex(this.mOuter.firstSelectedIndex) == this.mOuter.NC_NS + "Bookmark";
          case "cmd_bm_openinnewwindow":
            return true;
          case "cmd_bm_rename":
          case "cmd_bm_properties":
            if (bo.selection.count != 1)  
              return false;
            type = this.mOuter.getTypeAtIndex(this.mOuter.firstSelectedIndex);
            return type == this.mOuter.NC_NS + "Bookmark" || type == this.mOuter.NC_NS + "Folder";
          case "cmd_bm_cut":
            type = this.mOuter.getTypeAtIndex(this.mOuter.firstSelectedIndex);
            if (type != this.mOuter.NC_NS + "Bookmark" && type != this.mOuter.NC_NS + "BookmarkSeparator" && type != this.mOuter.NC_NS + "Folder")
              return false;
            return bo.selection.count > 0;
          case "cmd_bm_find":
            return true;
          case "cmd_bm_newbookmark":
          case "cmd_bm_newfolder":
          case "cmd_bm_newseparator":
            // This is not really correct because it gives the false impression
            // that it is possible to create an item as a child of some immutable folders
            // like IE Favorites, but it will do for now. 
            return true;
          case "cmd_bm_delete":
            // Determining whether or not the selection is mutable is handled by
            // the deletion routine.
            type = this.mOuter.getTypeAtIndex(this.mOuter.firstSelectedIndex);
            if (type != this.mOuter.NC_NS + "Bookmark" && type != this.mOuter.NC_NS + "BookmarkSeparator" &&
                type != this.mOuter.NC_NS + "Folder" && type != this.mOuter.NC_NS + "IEFavoriteFolder")
              return false;
            return bo.selection.count >= 1;
          case "cmd_bm_selectAll":
            // "Select All" is disabled when all visible rows are selected.
            // XXXben this could be cleverer. 
            return bo.view.rowCount != bo.selection.count;
          case "cmd_bm_copy":
            return bo.selection.count >= 1;
          case "cmd_bm_paste":
            return this.mOuter.canPaste();
          case "cmd_bm_setnewbookmarkfolder":
          case "cmd_bm_setpersonaltoolbarfolder":
          case "cmd_bm_setnewsearchfolder":
            if (bo.selection.count != 1) 
              return false;
            // XXXbar if a folder has more than one of these special attributes,
            // this won't work
            var folderURI;
            if (aCommand == "cmd_bm_setnewbookmarkfolder")
              folderURI = "NC:NewBookmarkFolder";
            else if (aCommand == "cmd_bm_setpersonaltoolbarfolder")
              folderURI = "NC:PersonalToolbarFolder";
            else
              folderURI = "NC:NewSearchFolder";
            return (this.mOuter.getTypeAtIndex(this.mOuter.firstSelectedIndex) == this.mOuter.NC_NS + "Folder")
                   && (this.mOuter.outlinerBuilder.getResourceAtIndex(this.mOuter.firstSelectedIndex).Value != folderURI);
          case "cmd_bm_import":
          case "cmd_bm_export":
            return true;
          case "cmd_bm_fileBookmark":
            if (bo.selection.count < 1) 
              return false;
            type = this.mOuter.getTypeAtIndex(this.mOuter.firstSelectedIndex);
            return type == this.mOuter.NC_NS + "Folder" || type == this.mOuter.NC_NS + "Bookmark";
          default:
            return false;
          }
        },

        doCommand: function BMOLController_doCommand(aCommand)
        {
          switch (aCommand) {
          case "cmd_bm_cut":
            this.mOuter.copySelection();
            this.mOuter.deleteSelection();
            break;
          case "cmd_bm_copy":
            this.mOuter.copySelection();
            break;
          case "cmd_bm_paste":
            this.mOuter.paste();
            break;
          case "cmd_bm_delete":
            this.mOuter.deleteSelection();
            break;
          case "cmd_bm_selectAll":
            this.mOuter.selectAll();
            break;
          case "cmd_bm_open":
            this.mOuter.openItem(null, false);
            break;
          case "cmd_bm_openfolder":
            this.mOuter.outlinerBoxObject.view.toggleOpenState(this.mOuter.currentIndex);
            break;
          case "cmd_bm_openinnewwindow":
            var type = this.mOuter.getTypeAtIndex(this.mOuter.currentIndex);
            if (type == this.mOuter.NC_NS + "Folder")
              this.mOuter.openFolderInNewWindow();
            else
              this.mOuter.openItem(null, true);
            break;
          case "cmd_bm_newbookmark":
            this.mOuter.addBookmark();
            break;
          case "cmd_bm_newfolder":
            this.mOuter.createNewFolder();
            break;
          case "cmd_bm_newseparator":
            this.mOuter.createNewSeparator();
            break;
          case "cmd_bm_find":
            this.mOuter.openFindDialog();
            break;
          case "cmd_bm_properties":
          case "cmd_bm_rename":
            this.mOuter.openPropertiesForItem();
            break;
          case "cmd_bm_setnewbookmarkfolder":
            this.mOuter.setAsNewBookmarkFolder();
            break;
          case "cmd_bm_setpersonaltoolbarfolder":
            this.mOuter.setAsPersonalToolbarFolder();
            break;         
          case "cmd_bm_setnewsearchfolder":
            this.mOuter.setAsNewSearchFolder();
            break;
          case "cmd_bm_import":
            this.mOuter.importBookmarks();
            break;
          case "cmd_bm_export":
            this.mOuter.exportBookmarks();
            break;
          case "cmd_bm_fileBookmark":
            this.mOuter.fileBookmark();
            break;
          default:
          }
        }
      })
      ]]></field>

      <method name="onCommandUpdate">
        <body><![CDATA[
          var commands = ["cmd_bm_properties", "cmd_bm_rename", "cmd_bm_copy",
                          "cmd_bm_paste", "cmd_bm_cut", "cmd_bm_delete",
                          "cmd_bm_setpersonaltoolbarfolder", 
                          "cmd_bm_setnewbookmarkfolder",
                          "cmd_bm_setnewsearchfolder", "cmd_bm_fileBookmark", 
                          "cmd_bm_openfolder"];
          for (var i = 0; i < commands.length; ++i) {
            var enabled = this.controller.isCommandEnabled(commands[i]);
            var commandNode = document.getElementById(commands[i]);
            if (commandNode) { 
              if (enabled) 
                commandNode.removeAttribute("disabled");
              else 
                commandNode.setAttribute("disabled", "true");
            }
          }
        ]]></body>
      </method>

      <method name="selectionChanged">
        <parameter name="aEvent" />
        <body><![CDATA[
        ]]></body>
      </method>

      <!-- nsIXULOutlinerBuilderObserver -->
      <field name="builderObserver"><![CDATA[
      ({
        mOuter: this,
        canDropOn: function(index)
        {
          return true;
        },
        canDropBeforeAfter: function(index, before)
        {
          return true;
        },
        onDrop: function(row, orientation)
        {  
          var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].getService().QueryInterface(Components.interfaces.nsIDragService);  
          var dragSession = dragService.getCurrentSession();
          if (!dragSession)
            return false;
          const kRDFCContractID = "@mozilla.org/rdf/container;1";
          const kRDFIID = Components.interfaces.nsIRDFContainer;
          var RDFC = Components.classes[kRDFCContractID].getService(kRDFIID);
          var rTarget = this.mOuter.outlinerBuilder.getResourceAtIndex(row);
          var parentIndex = this.mOuter.outlinerBoxObject.view.getParentIndex(row);
          var rContainer;
          if (parentIndex == -1)
            rContainer = this.mOuter.rdf.GetResource("NC:BookmarksRoot");
          else
            rContainer = this.mOuter.outlinerBuilder.getResourceAtIndex(parentIndex);
          var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
          var rBookmark = this.mOuter.rdf.GetResource(this.mOuter.NC_NS + "Bookmark");
          trans.addDataFlavor("moz/rdfitem");
          trans.addDataFlavor("text/x-moz-url");
          trans.addDataFlavor("text/unicode");
   
          var list = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
   
          var sourceUri;
          var sourceResource;
          var dirty = false;
          var additiveFlag = false;
          for (var i = 0; i < dragSession.numDropItems; ++i) {
            dragSession.getData(trans, i);
            var dataObj = {};
            var bestFlavor = {};
            var len = {};
            trans.getAnyTransferData(bestFlavor, dataObj, len);
            if (dataObj)
              dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsWString);
            if (!dataObj)
              continue;
            sourceUri = dataObj.data.substring(0, len.value);
            if (!sourceUri)
              continue;

            var sourceID = [], parentID = [], nameRequired = [], name = [];      
            nameRequired[i] = false;
            name[i] = null;
            switch (bestFlavor.value) {
            case "moz/rdfitem":
              var ix = sourceUri.indexOf("\n");
              sourceID[i] = ix >= 0 ? (parentID[i] = sourceUri.substr(ix+1), sourceUri.substr(0, ix)) : sourceUri;
              break;
            case "text/x-moz-url":
              ix = sourceUri.indexOf("\n");
              sourceID[i] = ix >= 0 ? (name[i] = sourceUri.substr(ix+1), sourceUri.substr(0, ix)) : sourceUri;
              break;
            case "text/unicode":
              sourceID[i] = sourceUri;
              nameRequired[i] = true;
              break;
            default: 
              continue;
            }
            var rSource = this.mOuter.rdf.GetResource(sourceID[i]);
            var rParent = parentID[i] ? this.mOuter.rdf.GetResource(parentID[i]) : null;

            const kBMDS = this.mOuter.bookmarksDS;
            var bmType = this.mOuter.resolveType(rSource);
            var rType = this.mOuter.rdf.GetResource(this.mOuter.RDF_NS + "type");
            if (!bmType) 
              kBMDS.Assert(rSource, rType, rBookmark, true);
            
            // prevent dropping folder within itself or one of its subfolders
            if (bmType == this.mOuter.NC_NS + "Folder") {
              var currRow = row;
              do {
                var currURI = this.mOuter.outlinerBuilder.getResourceAtIndex(currRow).Value;
                currRow = this.mOuter.outlinerBoxObject.view.getParentIndex(currRow);
              }
              while (currRow != -1 && currURI != "NC:BookmarksRoot" && currURI != rSource.Value);
              if (currURI == rSource.Value)
                return;
            }

            RDFC.Init(kBMDS, rContainer);
            var dropIx = RDFC.IndexOf(rTarget);
            // XXX if any of the following fails, the nodes are gone for good!
            const kDSIID = Components.interfaces.nsIDragService;
            const kCopyAction = kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_LINK;
            if (rParent) {
              if (!(dragSession.dragAction & kCopyAction)) {
                try {
                  RDFC.Init(kBMDS, rParent);
                  ix = RDFC.IndexOf(rSource);
                  if (ix >= 1) {
                    var index = this.mOuter.outlinerBuilder.getIndexOfResource(rSource);
                    RDFC.RemoveElementAt(ix, true);
                    // only need to decrement the target row if the index of the resource
                    // we're removing is before it 
                    if (index < row)
                      --row;
                  }
                }
                catch (ex) { }
              }
            }
    
            if (bmType == this.mOuter.NC_NS + "Folder") {
              // If we're going to copy a folder type, we need to clone the folder 
              // rather than just asserting the new node as a child of the drop folder.
              if (dragSession.dragAction & kCopyAction)
                rSource = BookmarksUtils.cloneFolder(rSource, rContainer, rTarget);
            }
            if (orientation == Components.interfaces.nsIOutlinerView.inDropBefore ||
                orientation == Components.interfaces.nsIOutlinerView.inDropAfter) {
              if (dropIx == -1) break;
              RDFC.Init(kBMDS, rContainer);
              RDFC.InsertElementAt(rSource, rParent ? dropIx : (orientation == Components.interfaces.nsIOutlinerView.inDropAfter ? ++dropIx : dropIx), true);
            }
            else {
              var cont = this.mOuter.outlinerBuilder.getResourceAtIndex(row);
              RDFC.Init(kBMDS, cont);
              RDFC.AppendElement(rSource); // drop on
            }
            dirty = true;

            //if (rParent) {
            //  gBookmarksShell.selectFolderItem(rContainer.Value, sourceID[i], additiveFlag);
            //  if (!additiveFlag) additiveFlag = true;
            //} 
            // If a name is supplied, we want to assert this information into the 
            // graph. E.g. user drags an internet shortcut to the app, we want to 
            // preserve not only the URL but the name of the shortcut. The other case
            // where we need to assert a name is when the node does not already exist
            // in the graph, in this case we'll just use the URL as the name.
            if (name[i] || nameRequired[i]) {
              var currentName = kBMDS.GetTarget(this.mOuter.rdf.GetResource(sourceID[i]), this.mOuter.rdf.GetResource(this.mOuter.NC_NS + "Name"), true);
              if (!currentName) {
                var rDefaultName = this.mOuter.rdf.GetLiteral(name[i] || sourceID[i]);
                if (rDefaultName) {
                  var rName = this.mOuter.rdf.GetResource(this.mOuter.NC_NS + "Name");
                  kBMDS.Assert(rSource, rName, rDefaultName, true);
                }
              }
            } 
            if (dirty)
              this.mOuter.flushBMDatasource();
          }
        },
        onToggleOpenState: function BMOLBuilderObserver_onToggleOpenState(aItemIndex)
        {
        },
        
        onCycleHeader: function BMOLBuilderObserver_onCycleHeader(aColumnID, aHeaderElement)
        {
        },
    
        onCycleCell: function BMOLBuilderObserver_onCycleCell(aItemIndex, aColumnID)
        {
        },
        
        onSelectionChanged: function BMOLBuilderObserver_onSelectionChanged()
        {
          this.mOuter.onCommandUpdate();
          const kStatusBar = document.getAnonymousElementByAttribute(this.mOuter, "anonid", "statusbar-text");
          const currentIndex = this.mOuter.currentIndex;
          var displayValue = "";
          if (kStatusBar && this.mOuter.outlinerBoxObject.selection.count == 1) {
            if (this.mOuter.outlinerBoxObject.view.isContainer(currentIndex)) {
              const kRDFCContractID = "@mozilla.org/rdf/container;1";
              const kRDFCIID = Components.interfaces.nsIRDFContainer;
              const kRDFC = Components.classes[kRDFCContractID].getService(kRDFCIID);
              const krSrc = this.mOuter.outlinerBuilder.getResourceAtIndex(currentIndex);
              try {
                kRDFC.Init(this.mOuter.db, krSrc);
                var count = kRDFC.GetCount();
                displayValue = this.mOuter._bundle.GetStringFromName("status_foldercount");
                displayValue = displayValue.replace(/%num_items%/, count);
              }
              catch (e) {
              }
            }
            else {
              try {
                displayValue = this.mOuter.db.GetTarget(this.mOuter.outlinerBuilder.getResourceAtIndex(currentIndex), this.mOuter.rdf.GetResource(this.mOuter.NC_NS + "URL"), true);
                displayValue = displayValue.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
              }
              catch (e) {
                displayValue = "";
              }
            }
            if (displayValue.substring(0, 3) == "NC:")
              displayValue = "";
            kStatusBar.label = displayValue;
          }
        },
        
        isEditable: function BMOLBuilderObserver_isEditable(aItemIndex, aColumnID)
        {
        },
        
        onSetCellText: function BMOLBuilderObserver_onSetCellText(aItemIndex, aColumnID, aValue)
        {
        },
        
        onPerformAction: function BMOLBuilderObserver_onPerformAction(aAction)
        {
        },
        
        onPerformActionOnRow: function BMOLBuilderObserver_onPerformActionOnRow(aAction, aItemIndex)
        {
        },
        
        onPerformActionOnCell: function BMOLBuilderObserver_onPerformActionOnCell(aAction, aItemIndex, aColumnID)
        {
        }
      })
      ]]></field>

      <!-- RDF utility functions required by base binding -->
      <property name="db">
        <getter><![CDATA[
          return this.outliner.database;
        ]]></getter>
      </property>
      
      <method name="doBookmarksCommand">
        <parameter name="aSourceURI"/>
        <parameter name="aCommand"/>
        <parameter name="aArgumentsArray"/>
        <body><![CDATA[
          var rCommand = this.rdf.GetResource(aCommand);
        
          var kSuppArrayContractID = "@mozilla.org/supports-array;1";
          var kSuppArrayIID = Components.interfaces.nsISupportsArray;
          var sourcesArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
          if (aSourceURI) {
            var rSource = this.rdf.GetResource(aSourceURI);
            sourcesArray.AppendElement (rSource);
          }
        
          var argsArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
          for (var i = 0; i < aArgumentsArray.length; ++i) {
            var rArc = this.rdf.GetResource(aArgumentsArray[i].property);
            argsArray.AppendElement(rArc);
            var rValue = null;
            if ("resource" in aArgumentsArray[i]) 
              rValue = this.rdf.GetResource(aArgumentsArray[i].resource);
            else
              rValue = this.rdf.GetLiteral(aArgumentsArray[i].literal);
            argsArray.AppendElement(rValue);
          }
      
          // Exec the command in the Bookmarks datasource. 
          this.bookmarksDS.DoCommand(sourcesArray, rCommand, argsArray);
        ]]></body>
      </method>
      
      <method name="selectAll">
        <body><![CDATA[
          this.outlinerBoxObject.selection.selectAll();
        ]]></body>
      </method>

      <method name="paste">
        <body><![CDATA[
          const kXferableContractID = "@mozilla.org/widget/transferable;1";
          const kXferableIID = Components.interfaces.nsITransferable;
          var xferable = Components.classes[kXferableContractID].createInstance(kXferableIID);
          xferable.addDataFlavor("moz/bookmarkclipboarditem");
          xferable.addDataFlavor("text/x-moz-url");
          xferable.addDataFlavor("text/unicode");
      
          const kClipboardContractID = "@mozilla.org/widget/clipboard;1";
          const kClipboardIID = Components.interfaces.nsIClipboard;
          var clipboard = Components.classes[kClipboardContractID].getService(kClipboardIID);
          clipboard.getData(xferable, kClipboardIID.kGlobalClipboard);
          
          var flavour = { };
          var data = { };
          var length = { };
          xferable.getAnyTransferData(flavour, data, length);
          var nodes = []; var names = [];
          data = data.value.QueryInterface(Components.interfaces.nsISupportsWString).data;
          switch (flavour.value) {
          case "moz/bookmarkclipboarditem":
            nodes = data.split("\n");
            break;
          case "text/x-moz-url":
            var ix = data.indexOf("\n");
            nodes.push(data.substring(0, ix != -1 ? ix : data.length));
            names.push(data.substring(ix));
            break;
          default: 
            return;
          }
          const kRDFContractID = "@mozilla.org/rdf/rdf-service;1";
          const kRDFIID = Components.interfaces.nsIRDFService;
          const ksRDF = Components.classes[kRDFContractID].getService(kRDFIID);
          var parentRes;
          const currentIndex = this.currentIndex;
          if (!this.outlinerBoxObject.view.isContainer(currentIndex))
            parentRes = this.parentRes;
          else
            parentRes = this.outlinerBuilder.getResourceAtIndex(currentIndex);

          const currentRes = this.currentRes;
          const kRDFCContractID = "@mozilla.org/rdf/container;1";
          const kRDFCIID = Components.interfaces.nsIRDFContainer;
          const ksRDFC = Components.classes[kRDFCContractID].getService(kRDFCIID);
          var additiveFlag = false;
          for (var i = 0; i < nodes.length; ++i) {
            if (!nodes[i]) continue;
            var rCurrent = ksRDF.GetResource(nodes[i]);
            const krTypeProperty = ksRDF.GetResource("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
            var rType = this.db.GetTarget(rCurrent, krTypeProperty, true);
            try {
              rType = rType.QueryInterface(Components.interfaces.nsIRDFResource);
            }
            catch (e) {
              try {
                rType = rType.QueryInterface(Components.interfaces.nsIRDFLiteral);
              }
              catch (e) {
                // OK, no type exists, so node does not exist in the graph. 
                // (e.g. user pastes url as text)
                // Do some housekeeping. 
                const krName = ksRDF.GetResource(names[i]);
                const krNameProperty = ksRDF.GetResource("http://home.netscape.com/NC-rdf#Name");
                const krBookmark = ksRDF.GetResource("http://home.netscape.com/NC-rdf#Bookmark");
                kBMDS.Assert(rCurrent, krNameProperty, krName, true);
                kBMDS.Assert(rCurrent, krTypeProperty, krBookmark, true);
              }
            }
            // If the node is a folder, then we need to create a new anonymous 
            // resource and copy all the arcs over.
            if (rType && rType.Value == "http://home.netscape.com/NC-rdf#Folder")
              rCurrent = BookmarksUtils.cloneFolder(rCurrent, parentRes, currentRes);
      
            // If this item already exists in this container, don't paste, as 
            // this will result in the creation of multiple copies in the datasource
            // but will not result in an update of the UI. (In Short: we don't
            // handle multiple bookmarks well)
            ksRDFC.Init(this.bookmarksDS, parentRes);
            ix = ksRDFC.IndexOf(rCurrent);
            if (ix != -1)
              continue;
      
            ix = ksRDFC.IndexOf(currentRes);
            if (ix != -1)
              ksRDFC.InsertElementAt(rCurrent, ix+1, true);
            else
              ksRDFC.AppendElement(rCurrent);
      
            this.flushBMDatasource();
          }
        ]]></body>
      </method>

      <method name="copySelection">
        <body><![CDATA[
          const kSuppArrayContractID = "@mozilla.org/supports-array;1";
          const kSuppArrayIID = Components.interfaces.nsISupportsArray;
          var itemArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
          const kRDFContractID = "@mozilla.org/rdf/rdf-service;1";
          const kRDFIID = Components.interfaces.nsIRDFService;
          const ksRDF = Components.classes[kRDFContractID].getService(kRDFIID);
          const kSuppWStringContractID = "@mozilla.org/supports-wstring;1";
          const kSuppWStringIID = Components.interfaces.nsISupportsWString;
          var bmstring = Components.classes[kSuppWStringContractID].createInstance(kSuppWStringIID);
          var unicodestring = Components.classes[kSuppWStringContractID].createInstance(kSuppWStringIID);
          var htmlstring = Components.classes[kSuppWStringContractID].createInstance(kSuppWStringIID);
        
          var sBookmarkItem = ""; var sTextUnicode = ""; var sTextHTML = "";
          var rangeCount = this.outlinerBoxObject.selection.getRangeCount();
          for (var i = rangeCount - 1; i >= 0; --i) {
            var rangeMin = {};
            var rangeMax = {};
            this.outlinerBoxObject.selection.getRangeAt(i, rangeMin, rangeMax);
            for (var j = rangeMax.value; j >= rangeMin.value; --j) {
              var res = this.outlinerBuilder.getResourceAtIndex(j);
              var urlRes = ksRDF.GetResource("http://home.netscape.com/NC-rdf#URL");
              var nameRes = ksRDF.GetResource("http://home.netscape.com/NC-rdf#Name");
              var url = this.db.GetTarget(res, urlRes, true);
              if (url)
                url = url.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
              var name = this.db.GetTarget(res, nameRes, true);
              name = name.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
              // XXXben is "\n" the best delimiter here? I think not. 
              sBookmarkItem += res.Value + "\n";
              sTextUnicode += url + "\n";
              sTextHTML += "<A HREF=\"" + url + "\">" + name + "</A>";
            }
          }
          
          const kXferableContractID = "@mozilla.org/widget/transferable;1";
          const kXferableIID = Components.interfaces.nsITransferable;
          var xferable = Components.classes[kXferableContractID].createInstance(kXferableIID);
      
          xferable.addDataFlavor("moz/bookmarkclipboarditem");
          bmstring.data = sBookmarkItem;
          xferable.setTransferData("moz/bookmarkclipboarditem", bmstring, sBookmarkItem.length*2)
          
          xferable.addDataFlavor("text/html");
          htmlstring.data = sTextHTML;
          xferable.setTransferData("text/html", htmlstring, sTextHTML.length*2)
          
          xferable.addDataFlavor("text/unicode");
          unicodestring.data = sTextUnicode;
          xferable.setTransferData("text/unicode", unicodestring, sTextUnicode.length*2)
          
          const kClipboardContractID = "@mozilla.org/widget/clipboard;1";
          const kClipboardIID = Components.interfaces.nsIClipboard;
          var clipboard = Components.classes[kClipboardContractID].getService(kClipboardIID);
          clipboard.setData(xferable, null, kClipboardIID.kGlobalClipboard);
        ]]></body>
      </method>

      <method name="deleteSelection">
        <body><![CDATA[
          const kRDFCContractID = "@mozilla.org/rdf/container;1";
          const kRDFContractID = "@mozilla.org/rdf/rdf-service;1";
          const kRDFIID = Components.interfaces.nsIRDFService;
          const kRDFCIID = Components.interfaces.nsIRDFContainer;
          const ksRDFC = Components.classes[kRDFCContractID].getService(kRDFCIID);
          const ksRDF = Components.classes[kRDFContractID].getService(kRDFIID);
 
          var nextIndex = 0; // Used to determine which item to select after the deletion
                             // is performed.

          // Whether or not the selection contains items which cannot be deleted.                             
          // var selectionContainsImmutableItems = false;

          var boxObject = this.outlinerBoxObject;
          var outlinerBuilder = this.outlinerBuilder;
          var rangeCount = this.outlinerBoxObject.selection.getRangeCount();
          
          var rangeMax = { };
          var rangeMin = { };
          
          // First, walk the selection and see if there's anything we /can't/ 
          // delete. If so, present a warning dialog listing the items that 
          // we can't remove. 
          const kIMDSContractID = "@mozilla.org/rdf/datasource;1?name=in-memory-datasource";
          const kIMDSIID = Components.interfaces.nsIRDFDataSource;
          var imDS = Components.classes[kIMDSContractID].getService(kIMDSIID);

          const kRDFCUContractID = "@mozilla.org/rdf/container-utils;1";
          const kRDFCUIID = Components.interfaces.nsIRDFContainerUtils;
          const kRDFCU = Components.classes[kRDFCUContractID].getService(kRDFCUIID);

          /*
          var immutableList = this.rdf.GetResource("NC:ImmutableBookmarkList");
          var container = kRDFCU.MakeSeq(imDS, immutableList);
          
          for (var i = rangeCount - 1; i >= 0; --i) {
            boxObject.selection.getRangeAt(i, rangeMin, rangeMax);
            for (var j = rangeMax.value; j >= rangeMin.value; --j) {
              var resource = outlinerBuilder.getResourceAtIndex(j);
              var type = this.getTypeAtIndex(j);
              if (resource.Value == "NC:BookmarksRoot" || type == this.NC_NS + "IEFavoriteFolder")
                continue;
              if (type != this.NC_NS + "Bookmark" && type != this.NC_NS + "BookmarkSeparator" && type != this.NC_NS + "Folder") {
                // This delete operation contains items which cannot be deleted
                // for one reason or another. Make a list of the items which
                // we can't delete, and show a dialog. 
                container.AppendElement(resource);
                selectionContainsImmutableItems = true;
                
                // Now we need to ensure that this item's parent chain is not
                // part of the current selection so that we don't delete that. We
                // preserve selection on any deletable items in the folder chain
                // however, and these are removed. 
                for (var parentIndex = boxObject.view.getParentIndex(j);
                     parentIndex != -1;
                     parentIndex = boxObject.view.getParentIndex(parentIndex)) {
                  // Note that we don't combine this with the check in the 
                  // |while| above, as this would mean that if you had a folder 
                  // hierarchy consisting of:
                  // 
                  //   Folder ->    [ selected ]
                  //        Folder ->    [ not selected ]
                  //             Immutable item [ selected ]
                  //
                  // This code would not work and the containing folder would be
                  // deleted. 
                  if (boxObject.selection.isSelected(parentIndex))
                    boxObject.selection.toggleSelect(parentIndex);
                }
              }
            }
          }
          
           // Present a dialog showing any items that we can't delete. 
           // if (selectionContainsImmutableItems) {
           //   openDialog("chrome://communicator/content/bookmarks/deleteBookmark.xul", "", "modal=yes,resizable=no", imDS);
            
            // Now clear the list of immutable items. 
            ksRDFC.Init(imDS, immutableList);
            var count = ksRDFC.GetCount();
            for (var i = 0; i < count; ++i)
              ksRDFC.RemoveElementAt(i, false);
          }
          */
          for (i = rangeCount - 1; i >= 0; --i) {
            rangeMax = { };
            rangeMin = { };
            boxObject.selection.getRangeAt(i, rangeMin, rangeMax);
            for (var j = rangeMax.value; j >= rangeMin.value; --j) {
              var resource = outlinerBuilder.getResourceAtIndex(j);
              if (resource.Value == "NC:BookmarksRoot")
                continue;
              var currType = this.getTypeAtIndex(j);
              if (currType == this.NC_NS + "IEFavoriteFolder") {
                const kPrefSvcContractID = "@mozilla.org/preferences;1";
                const kPrefSvcIID = Components.interfaces.nsIPref;
                const kPrefSvc = Components.classes[kPrefSvcContractID].getService(kPrefSvcIID);
                kPrefSvc.SetBoolPref("browser.bookmarks.import_system_favorites", false);
              }
              else if (currType != this.NC_NS + "Bookmark" && 
                       currType != this.NC_NS + "BookmarkSeparator" &&
                       currType != this.NC_NS + "Folder")
                continue;
              var parentIndex = boxObject.view.getParentIndex(j);
              var parent;
              if (parentIndex != -1)
                parent = outlinerBuilder.getResourceAtIndex(parentIndex);
              else {
                // assume its parent is the root
                parent = ksRDF.GetResource("NC:BookmarksRoot");
              }
              ksRDFC.Init(this.bookmarksDS, parent); 
              ksRDFC.RemoveElement(resource, true);
              nextIndex = j;
            }
          }

          // Select the next row          
          boxObject.selection.select(nextIndex);
        ]]></body>
      </method>
      <method name="openFindDialog">
        <body><![CDATA[
          openDialog("chrome://communicator/content/bookmarks/findBookmark.xul",
                     "FindBookmarksWindow",
                     "dialog=no,centerscreen,resizable=no,chrome,dependent");
        ]]></body>
      </method>
      <method name="canSendLink">
        <body><![CDATA[
          var selectedIndex = this.outlinerBoxObject.selection.currentIndex;
          return (this.outlinerBoxObject.selection.count == 1 &&
                  !this.outlinerBoxObject.view.isContainer(selectedIndex) &&
                  this.getTypeAtIndex(selectedIndex) != this.NC_NS + "BookmarkSeparator");
        ]]></body>
      </method>
      <method name="sendLink">
        <body><![CDATA[
          const currentRes = this.currentRes;
          var urlLiteral = this.db.GetTarget(currentRes, this.rdf.GetResource(this.NC_NS + "URL"), true).QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
          var nameLiteral = this.db.GetTarget(currentRes, this.rdf.GetResource(this.NC_NS + "Name"), true).QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
          sendLink(urlLiteral, nameLiteral);          
        ]]></body>
      </method>
      <method name="getAllCmds">
        <parameter name="aNodeID"/>
        <body><![CDATA[
          var type = this.resolveType(aNodeID);
          if (!type) {
            if (aNodeID == "NC:PersonalToolbarFolder" || aNodeID == "NC:BookmarksRoot")
              type = "http://home.netscape.com/NC-rdf#Folder";
            else
              return null;
          }
          var commands = [];
          // menu order:
          // 
          // bm_open
          // bm_openfolder
          // bm_openinnewwindow
          // /* bm_openinnewtab not yet supported */
          // ---------------------
          // /* bm_find removed */
          // bm_newfolder
          // ---------------------
          // bm_cut
          // bm_copy
          // bm_paste
          // bm_fileBookmark
          // ---------------------
          // bm_delete
          // bm_rename
          // ---------------------
          // bm_properties
          switch (type) {
          case "http://home.netscape.com/NC-rdf#BookmarkSeparator":
            commands = ["bm_newfolder", "bm_separator", 
                        "bm_cut", "bm_copy", "bm_paste", "bm_separator",
                        "bm_delete"];
            break;
          case "http://home.netscape.com/NC-rdf#Bookmark":
            commands = ["bm_open", "bm_openinnewwindow", /* "bm_openinnewtab", */ "bm_separator",
                        "bm_newfolder", "bm_separator",
                        "bm_cut", "bm_copy", "bm_paste", "bm_fileBookmark", "bm_separator",
                        "bm_delete", "bm_rename", "bm_separator",
                        "bm_properties"];
            break;
          case "http://home.netscape.com/NC-rdf#Folder":
            commands = ["bm_openfolder", "bm_openinnewwindow", "bm_separator", 
                        "bm_newfolder", "bm_separator",
                        "bm_cut", "bm_copy", "bm_paste", "bm_fileBookmark", "bm_separator",
                        "bm_delete", "bm_rename", "bm_separator",
                        "bm_properties"];
            break;
          case "http://home.netscape.com/NC-rdf#IEFavoriteFolder":
            commands = ["bm_openfolder", "bm_separator",
                        "bm_delete"];
            break;
          case "http://home.netscape.com/NC-rdf#IEFavorite":
            commands = ["bm_open", "bm_openinnewwindow", /* "bm_openinnewtab", */ "bm_separator",
                        "bm_copy"];
            break;
          case "http://home.netscape.com/NC-rdf#FileSystemObject":
            commands = ["bm_open", "bm_openinnewwindow", /* "bm_openinnewtab", */ "bm_separator",
                        "bm_copy"];
            break;
          default: 
            var source = this.rdf.GetResource(aNodeID);
            return this.db.GetAllCmds(source);
          }
          return new CommandArrayEnumerator(commands);
        ]]></body>
      </method>

      <method name="flattenEnumerator">
        <parameter name="aEnumerator"/>
        <body><![CDATA[
          if ("_index" in aEnumerator)
            return aEnumerator._inner;
          
          var temp = [];
          while (aEnumerator.hasMoreElements()) 
            temp.push(aEnumerator.getNext());
          return temp;
        ]]></body>
      </method>
      <method name="findCommonNodes">
        <parameter name="aNewArray"/>
        <parameter name="aOldArray"/>
        <body><![CDATA[
          var common = [];
          for (var i = 0; i < aNewArray.length; ++i) {
            for (var j = 0; j < aOldArray.length; ++j) {
              if (common.length > 0 && common[common.length-1] == aNewArray[i])
                continue;
              if (aNewArray[i] == aOldArray[j])
                common.push(aNewArray[i]);
            }
          }
          return common;
        ]]></body>
      </method>
      <method name="createMenuItem">
        <parameter name="aDisplayName"/>
        <parameter name="aCommandName"/>
        <parameter name="aSelectedIndex"/>
        <body><![CDATA[
          const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var xulElement = document.createElementNS(kXULNS, "menuitem");
          xulElement.setAttribute("cmd", aCommandName);
          xulElement.setAttribute("command", "cmd_" + aCommandName.substring(this.NC_NS_CMD.length));
          switch (aCommandName) {
          case this.NC_NS_CMD + "bm_open":
            xulElement.setAttribute("label", aDisplayName);
            xulElement.setAttribute("default", "true");
            break;
          case this.NC_NS_CMD + "bm_openfolder":
            aDisplayName = this.outlinerBoxObject.view.isContainerOpen(aSelectedIndex) ? this._bundle.GetStringFromName("cmd_bm_openfolder2") : aDisplayName;
            xulElement.setAttribute("label", aDisplayName);
            xulElement.setAttribute("default", "true");
            break;
          case this.NC_NS_CMD + "bm_renamebookmark":
            if (!document.popupNode.hasAttribute("type")) {
              xulElement.setAttribute("label", this._bundle.GetStringFromName("cmd_bm_renamebookmark2"));
              xulElement.setAttribute("cmd", (this.NC_NS_CMD + "bm_editurl"));
            }
            else
              xulElement.setAttribute("label", aDisplayName);
            break;
          default:
            xulElement.setAttribute("label", aDisplayName);
            break;
          }
          return xulElement;
        ]]></body>
      </method>
      <method name="getCommandName">
        <parameter name="aCommand"/>
        <body><![CDATA[
          var cmdName = aCommand.substring(this.NC_NS_CMD.length);
          try {
            // Note: this will succeed only if there's a string in the bookmarks
            //       string bundle for this command name. Otherwise, <xul:stringbundle/>
            //       will throw, we'll catch & stifle the error, and look up the command
            //       name in the datasource. 
            return this._bundle.GetStringFromName("cmd_" + cmdName);
          }
          catch (e) {
            // XXX - WORK TO DO HERE! (rjc will cry if we don't fix this) 
            // need to ask the ds for the commands for this node, however we don't
            // have the right params. This is kind of a problem. 
            dump("*** BAD! EVIL! WICKED! NO! ACK! ARGH! ORGH!\n");
            const rName = this.rdf.GetResource(this.NC_NS + "Name");
            const rSource = this.rdf.GetResource(aCommand);
            return this.db.GetTarget(rSource, rName, true).Value;
          }
        ]]></body>
      </method>
      <method name="createContextMenu">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var popup = aEvent.target;
          // clear out the old context menu contents (if any)
          while (popup.hasChildNodes()) 
            popup.removeChild(popup.firstChild);
          
          var popupNode = document.popupNode;
          var commonCommands = [];
          var rangeCount = this.outlinerBoxObject.selection.getRangeCount();
          for (var i = rangeCount - 1; i >= 0; --i) {
            var minRange = {};
            var maxRange = {};
            this.outlinerBoxObject.selection.getRangeAt(i, minRange, maxRange);          
            for (var j = maxRange.value; j >= minRange.value; --j) {
              var nodeURI = this.outlinerBuilder.getResourceAtIndex(j).Value;
              var commands = this.getAllCmds(nodeURI);
              if (!commands) {
                aEvent.preventDefault();
                return;
              }
              commands = this.flattenEnumerator(commands);
              if (!commonCommands.length) commonCommands = commands;
              commonCommands = this.findCommonNodes(commands, commonCommands);
            }
          }
          if (!commonCommands.length) {
            aEvent.preventDefault();
            return;
          }
          
          // Now that we should have generated a list of commands that is valid
          // for the entire selection, build a context menu.
          for (i = 0; i < commonCommands.length; ++i) {
            const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
            var currCommand = commonCommands[i].QueryInterface(Components.interfaces.nsIRDFResource).Value;
            var element = null;
            if (currCommand != this.NC_NS_CMD + "bm_separator") {
              var commandName = this.getCommandName(currCommand);
              var selectedIndex = this.outlinerBoxObject.selection.currentIndex;
              element = this.createMenuItem(commandName, currCommand, selectedIndex);
            }
            else if (i != 0 && i < commonCommands.length-1) {
              // Never append a separator as the first or last element in a context
              // menu.
              element = document.createElementNS(kXULNS, "menuseparator");
            }
            
            if (element) 
              popup.appendChild(element);
          }
        ]]></body>
      </method>
      <method name="openPropertiesForItem">
        <body><![CDATA[
          // XXX if not bookmarks separator
          var itemURI = this.outlinerBuilder.getResourceAtIndex(this.outlinerBoxObject.selection.currentIndex).Value;
          openDialog("chrome://communicator/content/bookmarks/bm-props.xul",
                     "", "centerscreen,chrome,dialog=no,resizable=no,dependent", 
                     itemURI);
        ]]></body>
      </method>
      <method name="setAsPersonalToolbarFolder">
        <body><![CDATA[
          var selectedURI = this.outlinerBuilder.getResourceAtIndex(this.outlinerBoxObject.selection.currentIndex).Value;
          var args = [];
          this.doBookmarksCommand(selectedURI, this.NC_NS_CMD + "setpersonaltoolbarfolder", args);

        ]]></body>
      </method>
      <method name="setAsNewSearchFolder">
        <body><![CDATA[
          var selectedURI = this.outlinerBuilder.getResourceAtIndex(this.outlinerBoxObject.selection.currentIndex).Value;
          var args = [];
          this.doBookmarksCommand(selectedURI, this.NC_NS_CMD + "setnewsearchfolder", args);
        ]]></body>
      </method>
      <method name="setAsNewBookmarkFolder">
        <body><![CDATA[
          var selectedURI = this.outlinerBuilder.getResourceAtIndex(this.outlinerBoxObject.selection.currentIndex).Value;
          var args = [];
          this.doBookmarksCommand(selectedURI, this.NC_NS_CMD + "setnewbookmarkfolder", args);
        ]]></body>
      </method>
      <method name="addBookmark">
        <body><![CDATA[
          const currentIndex = this.currentIndex;
          var parentIndex;
          if (this.outlinerBoxObject.view.rowCount) {
            if (this.outlinerBoxObject.view.isContainer(currentIndex))
              parentIndex = currentIndex;
            else
              parentIndex = this.outlinerBoxObject.view.getParentIndex(currentIndex);
          }
          else
            parentIndex = -1;
          var parentURI;
          if (parentIndex == -1)
            parentURI = this.rdf.GetResource("NC:BookmarksRoot").Value;
          else
            parentURI = this.outlinerBuilder.getResourceAtIndex(parentIndex).Value;
          openDialog("chrome://communicator/content/bookmarks/addBookmark.xul", "", 
                     "centerscreen,chrome,modal=yes,dialog=yes,resizable=no", null, null, parentURI, null, "newBookmark");
        ]]></body>
      </method>
      <method name="fileBookmark">
        <body><![CDATA[
          // XXX folder arg
          var rv = { selectedFolder: null };          
          openDialog("chrome://communicator/content/bookmarks/addBookmark.xul", "", 
                     "centerscreen,chrome,modal=yes,dialog=yes,resizable=yes", null, null, null, null, "selectFolder", rv);
          if (rv.selectedFolder) {
            var rangeCount = this.outlinerBoxObject.selection.getRangeCount();
            for (var k = rangeCount - 1; k >= 0; --k) {
              var rangeMin = {};
              var rangeMax = {};
              this.outlinerBoxObject.selection.getRangeAt(k, rangeMin, rangeMax);
              for (var i = rangeMax.value; i >= rangeMin.value; --i) {
                var selected = this.outlinerBuilder.getResourceAtIndex(i);
                if (selected.Value == rv.selectedFolder) 
                  return; // Selection contains the target folder. Just fail silently.
                var additiveFlag = false;
                var parentIndex = this.outlinerBoxObject.view.getParentIndex(i);
                var parentURI;
                if (parentIndex == -1)
                  parentURI = this.rdf.GetResource("NC:BookmarksRoot").Value;
                else
                  parentURI = this.outlinerBuilder.getResourceAtIndex(parentIndex).Value;
                this.moveBookmark(selected.Value, parentURI, rv.selectedFolder);
               // gBookmarksShell.selectFolderItem(rv.selectedFolder, selected.Value, additiveFlag);
                if (!additiveFlag) additiveFlag = true;
              }
            }
            this.flushBMDatasource();
          }
        ]]></body>
      </method>
      <method name="moveBookmark">
        <parameter name="aBookmarkURI"/>
        <parameter name="aFromFolderURI"/>
        <parameter name="aToFolderURI"/>
        <body><![CDATA[
          const kRDFCContractID = "@mozilla.org/rdf/container;1";
          const kRDFCIID = Components.interfaces.nsIRDFContainer;
          const kRDFC = Components.classes[kRDFCContractID].getService(kRDFCIID);
          const krSrc = this.rdf.GetResource(aBookmarkURI);
          const krOldParent = this.rdf.GetResource(aFromFolderURI);
          const krNewParent = this.rdf.GetResource(aToFolderURI);
          kRDFC.Init(this.bookmarksDS, krNewParent);
          kRDFC.AppendElement(krSrc);
          kRDFC.Init(this.bookmarksDS, krOldParent);
          kRDFC.RemoveElement(krSrc, true);
        ]]></body>
      </method>
      <method name="importBookmarks">
        <body><![CDATA[
          try {
            const kFilePickerContractID = "@mozilla.org/filepicker;1";
            const kFilePickerIID = Components.interfaces.nsIFilePicker;
            const kFilePicker = Components.classes[kFilePickerContractID].createInstance(kFilePickerIID);
         
            const kTitle = this._bundle.GetStringFromName("SelectImport");
            kFilePicker.init(window, kTitle, kFilePickerIID["modeOpen"]);
            kFilePicker.appendFilters(kFilePickerIID.filterHTML | kFilePickerIID.filterAll);
            var fileName;
            if (kFilePicker.show() != kFilePickerIID.returnCancel) {
              fileName = kFilePicker.fileURL.spec;
              if (!fileName) return;
            }
            else return;
          }
          catch (e) {
            return;
          }
          var seln = null;
          try {
            seln = this.outlinerBuilder.getResourceAtIndex(this.outlinerBoxObject.selection.currentIndex).Value;
          }
          catch(ex) {
            seln = this.rdf.GetResource("NC:BookmarksRoot").Value;
          }
          var args = [{ property: this.NC_NS + "URL", literal: fileName}];
          this.doBookmarksCommand(seln, this.NC_NS_CMD + "import", args);
        ]]></body>
      </method>
      <method name="exportBookmarks">
        <body><![CDATA[
          try {
            const kFilePickerContractID = "@mozilla.org/filepicker;1";
            const kFilePickerIID = Components.interfaces.nsIFilePicker;
            const kFilePicker = Components.classes[kFilePickerContractID].createInstance(kFilePickerIID);
            
            const kTitle = this._bundle.GetStringFromName("EnterExport");
            kFilePicker.init(window, kTitle, kFilePickerIID["modeSave"]);
            kFilePicker.appendFilters(kFilePickerIID.filterHTML | kFilePickerIID.filterAll);
            kFilePicker.defaultString = "bookmarks.html";
            var fileName;
            if (kFilePicker.show() != kFilePickerIID.returnCancel) {
              fileName = kFilePicker.fileURL.spec;
              if (!fileName) return;
            }
            else return;
          }
          catch (e) {
            return;
          }
          var seln = null;
          try {
            seln = this.outlinerBuilder.getResourceAtIndex(this.outlinerBoxObject.selection.currentIndex).Value;
          }
          catch(ex) {
            seln = this.rdf.GetResource("NC:BookmarksRoot").Value;
          }
          var args = [{ property: this.NC_NS + "URL", literal: fileName}];
          this.doBookmarksCommand(seln, this.NC_NS_CMD + "export", args);
        ]]></body>
      </method>
      <method name="openFolderInNewWindow">
        <body><![CDATA[
          var selectedIndex = this.outlinerBoxObject.selection.currentIndex;
          var selectedURI = this.outlinerBuilder.getResourceAtIndex(selectedIndex).Value;
          openDialog("chrome://communicator/content/bookmarks/bookmarks.xul", 
                     "", "chrome,all,dialog=no", selectedURI);
        ]]>
        </body>
      </method>
      <method name="createNewSeparator">
        <body><![CDATA[
          var currIndex = this.outlinerBoxObject.selection.currentIndex;
          if (currIndex == -1)
            currIndex = this.outlinerBoxObject.view.rowCount - 1;
          var parentIndex = this.outlinerBoxObject.view.getParentIndex(currIndex);
          var parentValue;
          if (parentIndex == -1)
            parentValue = this.rdf.GetResource("NC:BookmarksRoot").Value;
          else
            parentValue = this.outlinerBuilder.getResourceAtIndex(parentIndex).Value;
          args = [{ property: this.NC_NS + "parent", 
                    resource: parentValue }];
          this.doBookmarksCommand(this.outlinerBuilder.getResourceAtIndex(currIndex).Value, 
                                  this.NC_NS_CMD + "newseparator", args);
        ]]></body>
      </method>
      <method name="createNewFolder">
        <body><![CDATA[
          const kPromptSvcContractID = "@mozilla.org/embedcomp/prompt-service;1";
          const kPromptSvcIID = Components.interfaces.nsIPromptService;
          const kPromptSvc = Components.classes[kPromptSvcContractID].getService(kPromptSvcIID);
          var defaultValue  = this._bundle.GetStringFromName("ile_newfolder");
          var dialogTitle   = this._bundle.GetStringFromName("newfolder_dialog_title");
          var dialogMsg     = this._bundle.GetStringFromName("newfolder_dialog_msg");
          var stringValue   = { value: defaultValue };
          var relativeIndex = this.currentIndex;
          const currentIndex  = this.currentIndex;
          var parentRes;
          var isParent = false;
          if (kPromptSvc.prompt(window, dialogTitle, dialogMsg, stringValue, null, { value: 0 })) {
            if (currentIndex != -1 && currentIndex < this.outlinerBoxObject.view.rowCount) {
              // If it's an open container, the relative index should be that of the last child. 
              if (this.outlinerBoxObject.view.isContainerOpen(currentIndex)) {
                isParent = true;
                var index = currentIndex + 1;
                while (index < this.outlinerBoxObject.view.rowCount
                       && this.outlinerBoxObject.view.getParentIndex(index) == currentIndex)
                    ++index;
                  relativeIndex = index - 1;
              }
              if (isParent) {
                parentRes = this.outlinerBuilder.getResourceAtIndex(currentIndex);
              } else {
                parentRes = this.parentRes;
              }
            }
            else {
              parentRes = this.rdf.GetResource("NC:BookmarksRoot");
              relativeIndex = -1;
            }
            var args = [{ property: this.NC_NS + "parent",
                          resource: parentRes.Value },
                        { property: this.NC_NS + "Name",
                          literal:  stringValue.value }];
            this.bookmarksDS.AddObserver(this.newFolderRDFObserver);
            var relativeRes;
            if (relativeIndex != -1)
              relativeRes = this.outlinerBuilder.getResourceAtIndex(relativeIndex).Value;
            else
              relativeRes = this.rdf.GetResource("NC:BookmarksRoot").Value;
   
            this.doBookmarksCommand(relativeRes, this.NC_NS_CMD + "newfolder", args);
            this.bookmarksDS.RemoveObserver(this.newFolderRDFObserver);
          }
      ]]></body>
      </method>
     
      <method name="validOpenClickConditions">
      <parameter name="aEvent"/>
        <body><![CDATA[
          if (aEvent.button != 0)
            return false;
          var row = {};
          var col = {};
          var obj = {};
          this.outlinerBoxObject.getCellAt(aEvent.clientX, aEvent.clientY, row, col, obj);
          if (row.value == -1 || obj.value == "twisty")
            return false;
          return true;
        ]]></body>
      </method>

      // requires utilityOverlay.js if opening in new window for opentopwin()
      <method name="openItem">
      <parameter name="aEvent"/>
      <parameter name="aInNewWindow"/>
        <body><![CDATA[
          if (this.outlinerBoxObject.view.isContainer(this.outlinerBoxObject.selection.currentIndex))
            return;
    
          var urlRes, urlValue;
          if (aEvent && aEvent.altKey)
            this.openPropertiesForItem();
          else if (aInNewWindow) {
            var seln = this.outlinerBoxObject.selection;
            var rangeCount = seln.getRangeCount();
            for (var i = rangeCount - 1; i >= 0; --i) {
              var rangeMin = { };
              var rangeMax = { };
              seln.getRangeAt(i, rangeMin, rangeMax);
              for (var k = rangeMax.value; k >= rangeMin.value; --k) {
                urlRes = this.rdf.GetResource(this.NC_NS + "URL");
                urlValue = this.db.GetTarget(this.outlinerBuilder.getResourceAtIndex(k), urlRes, true).QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
                // Ignore "NC:" and empty urls
                if (!urlValue || urlValue.substring(0,3) == "NC:") return;
                openDialog (this._browserURL, "_blank", "chrome,all,dialog=no", urlValue);
              }
            }
          }
          else {
            urlRes = this.rdf.GetResource(this.NC_NS + "URL");
            urlValue = this.db.GetTarget(this.currentRes, urlRes, true).QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
            // Ignore "NC:" and empty urls
            if (!urlValue || urlValue.substring(0,3) == "NC:") return;
            openTopWin(urlValue);
          }
          if (aEvent) 
            aEvent.preventBubble();
        ]]></body>
      </method>
      <method name="canPaste">
        <body><![CDATA[
          // XXXben
          return true;
        ]]></body>
      </method>
      
      <method name="newBookmark">
        <body><![CDATA[
          var insertAt = this.getNextRowIndex();
          openDialog("chrome://communicator/content/bookmarks/addBookmark.xul", "", 
                     "centerscreen,chrome,modal=yes,dialog=yes,resizable=no", null, null, folder, null, "newBookmark");
          
        ]]></body>
      </method>
    </implementation>
  </binding>
  <!-- Full Bookmarks Outliner, multi-columned -->
  <!-- Localize column labels! -->
  <binding id="bookmarks-outliner-full" extends="chrome://communicator/content/bookmarks/bookmarks.xml#bookmarks-outliner">
    <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xbl="http://www.mozilla.org/xbl"
                 contextmenu="_child">
      <!-- XXXben need focus event handler for cmd update -->
        <menupopup id="bmContext" 
                   onpopupshowing="this.parentNode.createContextMenu(event);"/>
      <vbox flex="1">
        <outliner anonid="bookmarks-outliner" flex="1" class="plain" enableColumnDrag="true"
                  datasources="rdf:bookmarks rdf:internetsearch rdf:files rdf:localsearch" ref="NC:BookmarksRoot" flags="dont-build-content"
                  onkeypress="if (event.keyCode == 13) this.parentNode.parentNode.openItem(event, true);"
                  ondblclick="if (event.originalTarget.localName == 'outlinerchildren') this.parentNode.parentNode.openItem(event, false);"
                  ondraggesture="if (event.originalTarget.localName == 'outlinerchildren') nsDragAndDrop.startDrag(event, this.parentNode.parentNode.DNDObserver);"
                  onselect="this.parentNode.parentNode.view.selectionChanged();">
          <template>
            <rule rdf:type="http://home.netscape.com/NC-rdf#BookmarkSeparator">
              <outlinerchildren>
                <outlineritem uri="rdf:*">
                  <outlinerrow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type separator"/>
                </outlineritem>
              </outlinerchildren>
            </rule>
            <rule>
              <outlinerchildren>
                <outlineritem uri="rdf:*">
                  <outlinerrow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type rdf:http://home.netscape.com/NC-rdf#loading rdf:http://home.netscape.com/WEB-rdf#status">
                    <outlinercell label="rdf:http://home.netscape.com/NC-rdf#Name" />
                    <outlinercell label="rdf:http://home.netscape.com/NC-rdf#URL" />
                    <outlinercell label="rdf:http://home.netscape.com/NC-rdf#ShortcutURL" />
                    <outlinercell label="rdf:http://home.netscape.com/NC-rdf#Description" />
                    <outlinercell label="rdf:http://home.netscape.com/WEB-rdf#LastVisitDate"/>
                    <outlinercell label="rdf:http://home.netscape.com/NC-rdf#BookmarkAddDate" />
                    <outlinercell label="rdf:http://home.netscape.com/NC-rdf#LastModifiedDate" />
                  </outlinerrow>
                </outlineritem>
              </outlinerchildren>
            </rule>
          </template>
          <outlinercols>
            <outlinercol id="Name" label="&outlinercol.name.label;" flex="1" primary="true" class="sortDirectionIndicator" persist="width hidden sortActive sortDirection ordinal" sort="rdf:http://home.netscape.com/NC-rdf#Name"
                         sortActive="true" sortDirection="none"/>
            <splitter class="tree-splitter" />
            <outlinercol id="URL" label="&outlinercol.url.label;" flex="1" class="sortDirectionIndicator" sort="rdf:http://home.netscape.com/NC-rdf#URL" persist="width hidden sortActive sortDirection ordinal" />
            <splitter class="tree-splitter" />
            <outlinercol id="ShortcutURL" label="&outlinercol.shortcut.label;" hidden="true" flex="1" class="sortDirectionIndicator" persist="hidden width sortActive sortDirection ordinal" sort="rdf:http://home.netscape.com/NC-rdf#ShortcutURL"/>
            <splitter class="tree-splitter"/>
            <outlinercol id="Description" label="&outlinercol.description.label;" hidden="true" flex="1" class="sortDirectionIndicator" persist="hidden width sortActive sortDirection ordinal" sort="rdf:http://home.netscape.com/NC-rdf#Description"/>
            <splitter class="tree-splitter"/>
            <outlinercol id="AddDate" label="&outlinercol.addedon.label;" hidden="true" flex="1" class="sortDirectionIndicator" sort="rdf:http://home.netscape.com/NC-rdf#BookmarkAddDate" persist="width hidden sortActive sortDirection ordinal" />
            <splitter class="tree-splitter" />
            <outlinercol id="LastModDate" label="&outlinercol.lastmod.label;" hidden="true" flex="1" class="sortDirectionIndicator" sort="rdf:http://home.netscape.com/NC-rdf#LastModifiedDate" persist="width hidden sortActive sortDirection ordinal" />
            <splitter class="tree-splitter" />
            <outlinercol id="LastVisitDate" label="&outlinercol.lastvisit.label;" hidden="true" flex="1" class="sortDirectionIndicator" sort="rdf:http://home.netscape.com/NC-rdf#LastVisitDate" persist="width hidden sortActive sortDirection ordinal" />
          </outlinercols>
        </outliner>
        <statusbar class="chromeclass-status" inherits="hidden=hidestatusbar" hidden="false">
          <statusbarpanel anonid="statusbar-text" flex="1"/>
        </statusbar>
      </vbox>
    </xbl:content>
  </binding>
  <!-- Single column outliner -->
  <binding id="bookmarks-outliner-name" extends="chrome://communicator/content/bookmarks/bookmarks.xml#bookmarks-outliner">
    <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xbl="http://www.mozilla.org/xbl"
                 contextmenu="_child">
      <menupopup id="bmContext" 
                 onpopupshowing="this.parentNode.createContextMenu(event);"/>
      <outliner anonid="bookmarks-outliner" flex="1" hidecolumnpicker="true"
                datasources="rdf:bookmarks rdf:internetsearch rdf:files rdf:localsearch" ref="NC:BookmarksRoot" flags="dont-build-content"
                onkeypress="if (event.keyCode == 13) this.parentNode.openItem(event, false);"
                ondraggesture="if (event.originalTarget.localName == 'outlinerchildren') nsDragAndDrop.startDrag(event, this.parentNode.DNDObserver);"
                onclick="if (this.parentNode.validOpenClickConditions(event)) this.parentNode.openItem(event, false);"
                onselect="this.parentNode.outlinerBoxObject.view.selectionChanged();">
        <template>
          <rule rdf:type="http://home.netscape.com/NC-rdf#BookmarkSeparator">
            <outlinerchildren>
              <outlineritem uri="rdf:*">
                <outlinerrow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type separator" />
              </outlineritem>
            </outlinerchildren>
          </rule>
          <rule>
            <outlinerchildren>
              <outlineritem uri="rdf:*">
                <outlinerrow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type rdf:http://home.netscape.com/NC-rdf#loading rdf:http://home.netscape.com/WEB-rdf#status">
                  <outlinercell label="rdf:http://home.netscape.com/NC-rdf#Name" />
                </outlinerrow>
              </outlineritem>
            </outlinerchildren>
          </rule>
        </template>
        <outlinercols>
          <outlinercol id="Name" label="&outlinercol.name.label;" flex="1" primary="true" class="sortDirectionIndicator" persist="width hidden sortActive sortDirection" sort="rdf:http://home.netscape.com/NC-rdf#Name"
                       sortActive="true" sortDirection="none"/>
        </outlinercols>
      </outliner>
    </xbl:content>
  </binding>
  <!-- Outliner with folders only -->
  <binding id="bookmarks-outliner-folders" extends="chrome://communicator/content/bookmarks/bookmarks.xml#bookmarks-outliner">
    <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xbl="http://www.mozilla.org/xbl">
      <outliner anonid="bookmarks-outliner" flex="1" hidecolumnpicker="true"
                datasources="rdf:bookmarks rdf:internetsearch rdf:files rdf:localsearch" ref="NC:BookmarksRoot" flags="dont-build-content"
                onselect="this.parentNode.outlinerBoxObject.view.selectionChanged();">
        <template>
          <rule iscontainer="true">
            <outlinerchildren>
              <outlineritem uri="rdf:*">
                <outlinerrow properties="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type rdf:http://home.netscape.com/NC-rdf#loading rdf:http://home.netscape.com/WEB-rdf#status">
                  <outlinercell label="rdf:http://home.netscape.com/NC-rdf#Name" />
                </outlinerrow>
              </outlineritem>
            </outlinerchildren>
          </rule>
        </template>
        <outlinercols>
          <outlinercol id="Name" label="&outlinercol.name.label;" flex="1" primary="true"
                       class="sortDirectionIndicator" persist="width hidden sortActive sortDirection"
                       sort="rdf:http://home.netscape.com/NC-rdf#Name"
                       sortActive="true" sortDirection="none"/>
        </outlinercols>
      </outliner>
    </xbl:content>
  </binding>
</bindings>
<!--

API:
  - root ref
  - datasources
  - columns
  - click count
  - sort
  - selection
  - default action oncommand
  - context menu
  - insert, remove bookmarks
  - 

//-->
<!-- 
  ILE: 
    - impl ILE on outliner.xml
//-->
<!--
  Command handling:
    - impl |nsIController| 
    - actual controller implemented by client which intercepts commands and provides
      special handling for things like open in new browser etc. 
    - client can alternatively use the built in controller for default functionality.
    - decisions:
        Implementation pattern:
        client: 
        (A) bookmarksUIElement.prototype.doCommand = function () { .. }
          OR
        (B) var ctrlr = { doCommand: function () { bookmarksUIElement.doCommand(); }
          Tend to prefer (B) as it supports delegation
//-->