home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2005 October / Gamestar_77_2005-10_dvd.iso / Programy / nsb-install-8-0.exe / chrome / browser.jar / content / browser / bookmarks / bookmarks.js < prev    next >
Encoding:
Text File  |  2005-07-29  |  69.3 KB  |  2,081 lines

  1.  
  2. var NC_NS, WEB_NS, RDF_NS, XUL_NS, NC_NS_CMD;
  3.  
  4. // definition of the services frequently used for bookmarks
  5. var kRDFContractID;
  6. var kRDFSVCIID;
  7. var kRDFRSCIID;
  8. var kRDFLITIID;
  9. var RDF;
  10.  
  11. var kRDFCContractID;
  12. var kRDFCIID;
  13. var RDFC;
  14.  
  15. var kRDFCUContractID;
  16. var kRDFCUIID;
  17. var RDFCU;
  18.  
  19. var BMDS;
  20. var kBMSVCIID;
  21. var BMSVC;
  22.  
  23. var kPREFContractID;
  24. var kPREFIID;
  25. var PREF;
  26.  
  27. var kSOUNDContractID;
  28. var kSOUNDIID;
  29. var SOUND;
  30.  
  31. var kWINDOWContractID;
  32. var kWINDOWIID;
  33. var WINDOWSVC;
  34.  
  35. var kDSContractID;
  36. var kDSIID;
  37. var DS;
  38.  
  39. var kIOContractID;
  40. var kIOIID;
  41. var IOSVC;
  42.  
  43. // should be moved in a separate file
  44. function initServices()
  45. {
  46.   NC_NS     = "http://home.netscape.com/NC-rdf#";
  47.   WEB_NS    = "http://home.netscape.com/WEB-rdf#";
  48.   RDF_NS    = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
  49.   XUL_NS    = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  50.   NC_NS_CMD = NC_NS + "command?cmd=";
  51.  
  52.   kRDFContractID   = "@mozilla.org/rdf/rdf-service;1";
  53.   kRDFSVCIID       = Components.interfaces.nsIRDFService;
  54.   kRDFRSCIID       = Components.interfaces.nsIRDFResource;
  55.   kRDFLITIID       = Components.interfaces.nsIRDFLiteral;
  56.   RDF              = Components.classes[kRDFContractID].getService(kRDFSVCIID);
  57.  
  58.   kRDFCContractID  = "@mozilla.org/rdf/container;1";
  59.   kRDFCIID         = Components.interfaces.nsIRDFContainer;
  60.   RDFC             = Components.classes[kRDFCContractID].createInstance(kRDFCIID);
  61.  
  62.   kRDFCUContractID = "@mozilla.org/rdf/container-utils;1";
  63.   kRDFCUIID        = Components.interfaces.nsIRDFContainerUtils;
  64.   RDFCU            = Components.classes[kRDFCUContractID].getService(kRDFCUIID);
  65.  
  66.   kPREFContractID  = "@mozilla.org/preferences-service;1";
  67.   kPREFIID         = Components.interfaces.nsIPrefService;
  68.   PREF             = Components.classes[kPREFContractID].getService(kPREFIID)
  69.                                .getBranch(null);
  70.  
  71.   kSOUNDContractID = "@mozilla.org/sound;1";
  72.   kSOUNDIID        = Components.interfaces.nsISound;
  73.   SOUND            = Components.classes[kSOUNDContractID].createInstance(kSOUNDIID);
  74.  
  75.   kWINDOWContractID = "@mozilla.org/appshell/window-mediator;1";
  76.   kWINDOWIID        = Components.interfaces.nsIWindowMediator;
  77.   WINDOWSVC         = Components.classes[kWINDOWContractID].getService(kWINDOWIID);
  78.  
  79.   kDSContractID     = "@mozilla.org/widget/dragservice;1";
  80.   kDSIID            = Components.interfaces.nsIDragService;
  81.   DS                = Components.classes[kDSContractID].getService(kDSIID);
  82.  
  83.   kIOContractID     = "@mozilla.org/network/io-service;1";
  84.   kIOIID            = Components.interfaces.nsIIOService;
  85.   IOSVC             = Components.classes[kIOContractID].getService(kIOIID);
  86. }
  87.  
  88. function initBMService()
  89. {
  90.   kBMSVCIID = Components.interfaces.nsIBookmarksService;
  91.   BMDS  = RDF.GetDataSource("rdf:bookmarks");
  92.   BMSVC = BMDS.QueryInterface(kBMSVCIID);
  93.   BookmarkTransaction.prototype.RDFC = RDFC;
  94.   BookmarkTransaction.prototype.BMDS = BMDS;
  95. }
  96.  
  97. /**
  98.  * XXX - 24 Jul 04
  99.  * If you add a command that needs to run from the main browser window,
  100.  * it needs to be added to browser/base/content/browser-sets.inc as well!
  101.  *
  102.  * XXX - 04/16/01
  103.  *  ACK! massive command name collision problems are causing big issues
  104.  *  in getting this stuff to work in the Navigator window. For sanity's 
  105.  *  sake, we need to rename all the commands to be of the form cmd_bm_*
  106.  *  otherwise there'll continue to be problems. For now, we're just 
  107.  *  renaming those that affect the personal toolbar (edit operations,
  108.  *  which were clashing with the textfield controller)
  109.  *
  110.  * There are also several places that need to be updated if you need
  111.  * to change a command name. 
  112.  *   1) the controller...
  113.  *      - in bookmarksTree.xml if the command is tree-specifc
  114.  *      - in bookmarksMenu.js if the command is DOM-specific
  115.  *      - in bookmarks.js otherwise
  116.  *   2) the command nodes in the overlay or xul file
  117.  *   3) the command human-readable name key in bookmarks.properties
  118.  *   4) the function 'getCommands' in bookmarks.js
  119.  */
  120.  
  121. var BookmarksCommand = {
  122.  
  123.   /////////////////////////////////////////////////////////////////////////////
  124.   // This method constructs a menuitem for a context menu for the given command.
  125.   // This is implemented by the client so that it can intercept menuitem naming
  126.   // as appropriate.
  127.   createMenuItem: function (aDisplayName, aAccessKey, aCommandName, aSelection)
  128.   {
  129.     var xulElement = document.createElementNS(XUL_NS, "menuitem");
  130.     xulElement.setAttribute("cmd", aCommandName);
  131.     var cmd = "cmd_" + aCommandName.substring(NC_NS_CMD.length);
  132.     xulElement.setAttribute("command", cmd);
  133.     
  134.     if (aCommandName == NC_NS_CMD + "bm_expandfolder") {
  135.       var shouldCollapse = true;
  136.       for (var i=0; i<aSelection.length; ++i)
  137.         if (!aSelection.isExpanded[i])
  138.           shouldCollapse = false;
  139.  
  140.       if (shouldCollapse) {
  141.         aDisplayName = BookmarksUtils.getLocaleString("cmd_bm_collapsefolder");
  142.         aAccessKey   = BookmarksUtils.getLocaleString("cmd_bm_collapsefolder_accesskey");
  143.       }
  144.     }
  145.  
  146.     xulElement.setAttribute("label", aDisplayName);
  147.     xulElement.setAttribute("accesskey", aAccessKey);
  148.     return xulElement;
  149.   },
  150.  
  151.   /////////////////////////////////////////////////////////////////////////////
  152.   // Fill a context menu popup with menuitems that are appropriate for the current
  153.   // selection.
  154.   createContextMenu: function (aEvent, aSelection, aDS)
  155.   {
  156.     if (aSelection == undefined) {
  157.       aEvent.preventDefault();
  158.       return;
  159.     }
  160.  
  161.     var popup = aEvent.target;
  162.     // clear out the old context menu contents (if any)
  163.     while (popup.hasChildNodes()) 
  164.       popup.removeChild(popup.firstChild);
  165.         
  166.     var commonCommands = [];
  167.     for (var i = 0; i < aSelection.length; ++i) {
  168.       var commands = this.getCommands(aSelection.item[i], aSelection.parent[i], aDS);
  169.       if (!commands) {
  170.         aEvent.preventDefault();
  171.         return;
  172.       }
  173.       commands = this.flattenEnumerator(commands);
  174.       if (!commonCommands.length) commonCommands = commands;
  175.       commonCommands = this.findCommonNodes(commands, commonCommands);
  176.     }
  177.  
  178.     if (!commonCommands.length) {
  179.       aEvent.preventDefault();
  180.       return;
  181.     }
  182.     
  183.     // Now that we should have generated a list of commands that is valid
  184.     // for the entire selection, build a context menu.
  185.     for (i = 0; i < commonCommands.length; ++i) {
  186.       var currCommand = commonCommands[i].QueryInterface(kRDFRSCIID).Value;
  187.       var element = null;
  188.       if (currCommand != NC_NS_CMD + "bm_separator") {
  189.         var commandName = this.getCommandName(currCommand);
  190.         var accessKey = this.getAccessKey(currCommand);
  191.         element = this.createMenuItem(commandName, accessKey, currCommand, aSelection);
  192.       }
  193.       else if (i != 0 && i < commonCommands.length-1) {
  194.         // Never append a separator as the first or last element in a context
  195.         // menu.
  196.         element = document.createElementNS(XUL_NS, "menuseparator");
  197.       }
  198.       if (element) 
  199.         popup.appendChild(element);
  200.     }
  201.  
  202.     switch (popup.firstChild.getAttribute("command")) {
  203.     case "cmd_bm_open":
  204.     case "cmd_bm_expandfolder":
  205.       popup.firstChild.setAttribute("default", "true");
  206.     }
  207.   },
  208.   
  209.   /////////////////////////////////////////////////////////////////////////////
  210.   // Given two unique arrays, return an array that contains only the elements
  211.   // common to both. 
  212.   findCommonNodes: function (aNewArray, aOldArray)
  213.   {
  214.     var common = [];
  215.     for (var i = 0; i < aNewArray.length; ++i) {
  216.       for (var j = 0; j < aOldArray.length; ++j) {
  217.         if (common.length > 0 && common[common.length-1] == aNewArray[i])
  218.           continue;
  219.         if (aNewArray[i] == aOldArray[j])
  220.           common.push(aNewArray[i]);
  221.       }
  222.     }
  223.     return common;
  224.   },
  225.  
  226.   flattenEnumerator: function (aEnumerator)
  227.   {
  228.     if ("_index" in aEnumerator)
  229.       return aEnumerator._inner;
  230.     
  231.     var temp = [];
  232.     while (aEnumerator.hasMoreElements()) 
  233.       temp.push(aEnumerator.getNext());
  234.     return temp;
  235.   },
  236.   
  237.   /////////////////////////////////////////////////////////////////////////////
  238.   // For a given URI (a unique identifier of a resource in the graph) return 
  239.   // an enumeration of applicable commands for that URI. 
  240.   getCommands: function (aNodeID, aParent, aDS)
  241.   {
  242.     var type = BookmarksUtils.resolveType(aNodeID, aDS);
  243.     if (!type)
  244.       return null;
  245.  
  246.     var ptype = null;
  247.     if (aParent) {
  248.       ptype = BookmarksUtils.resolveType(aParent, aDS);
  249.       if (ptype == "Livemark") {
  250.         type = "LivemarkBookmark";
  251.       }
  252.     }
  253.  
  254.     var commands = [];
  255.     // menu order:
  256.     // 
  257.     // bm_expandfolder
  258.     // bm_open, bm_openfolder
  259.     // bm_openinnewwindow
  260.     // bm_openinnewtab
  261.     // ---------------------
  262.     // bm_newfolder
  263.     // ---------------------
  264.     // cut
  265.     // copy
  266.     // paste
  267.     // ---------------------
  268.     // delete
  269.     // ---------------------
  270.     // bm_refreshlivemark
  271.     // bm_sortbyname
  272.     // ---------------------
  273.     // bm_properties
  274.     switch (type) {
  275.     case "BookmarkSeparator":
  276.       commands = ["bm_newbookmark", "bm_newfolder", "bm_newseparator", "bm_separator",
  277.                   "cut", "copy", "paste", "bm_separator",
  278.                   "delete", "bm_separator",
  279.                   "bm_sortbyname", "bm_separator",
  280.                   "bm_properties"];
  281.       break;
  282.     case "Bookmark":
  283.       commands = ["bm_open", "bm_openinnewwindow", "bm_openinnewtab", "bm_separator",
  284.                   "bm_newbookmark", "bm_newfolder", "bm_newseparator", "bm_separator",
  285.                   "cut", "copy", "paste", "bm_separator",
  286.                   "delete", "bm_separator",
  287.                   "bm_sortbyname", "bm_separator",
  288.                   "bm_properties"];
  289.       break;
  290.     case "Folder":
  291.     case "PersonalToolbarFolder":
  292.       commands = ["bm_expandfolder", "bm_openfolder", "bm_managefolder", "bm_separator", 
  293.                   "bm_newbookmark", "bm_newfolder", "bm_newseparator", "bm_separator",
  294.                   "cut", "copy", "paste", "bm_separator",
  295.                   "delete", "bm_separator",
  296.                   "bm_sortbyname", "bm_separator",
  297.                   "bm_properties"];
  298.       break;
  299.     case "IEFavoriteFolder":
  300.       commands = ["bm_expandfolder", "bm_separator", "delete"];
  301.       break;
  302.     case "IEFavorite":
  303.       commands = ["bm_open", "bm_openinnewwindow", "bm_openinnewtab", "bm_separator",
  304.                   "copy"];
  305.       break;
  306.     case "FileSystemObject":
  307.       commands = ["bm_open", "bm_openinnewwindow", "bm_openinnewtab", "bm_separator",
  308.                   "copy"];
  309.       break;
  310.     case "Livemark":
  311.       commands = ["bm_expandfolder", "bm_openfolder", "bm_separator",
  312.                   "cut", "copy", "bm_separator",
  313.                   "delete", "bm_separator",
  314.                   "bm_refreshlivemark", "bm_sortbyname", "bm_separator",
  315.                   "bm_properties"];
  316.       break;
  317.     case "LivemarkBookmark":
  318.       commands = ["bm_open", "bm_openinnewwindow", "bm_openinnewtab", "bm_separator",
  319.                   "copy"];
  320.       break;
  321.     case "ImmutableBookmark":
  322.       commands = ["bm_open", "bm_openinnewwindow", "bm_openinnewtab"];
  323.       break;
  324.     default: 
  325.       commands = [];
  326.     }
  327.  
  328.     return new CommandArrayEnumerator(commands);
  329.   },
  330.   
  331.   /////////////////////////////////////////////////////////////////////////////
  332.   // Retrieve the human-readable name for a particular command. Used when 
  333.   // manufacturing a UI to invoke commands.
  334.   getCommandName: function (aCommand) 
  335.   {
  336.     var cmdName = aCommand.substring(NC_NS_CMD.length);
  337.     return BookmarksUtils.getLocaleString ("cmd_" + cmdName);
  338.   },
  339.  
  340.   /////////////////////////////////////////////////////////////////////////////
  341.   // Retrieve the access key for a particular command. Used when 
  342.   // manufacturing a UI to invoke commands.
  343.   getAccessKey: function (aCommand) 
  344.   {
  345.     var cmdName = aCommand.substring(NC_NS_CMD.length);
  346.     return BookmarksUtils.getLocaleString ("cmd_" + cmdName + "_accesskey");
  347.   },
  348.   
  349.   ///////////////////////////////////////////////////////////////////////////
  350.   // Execute a command with the given source and arguments
  351.   doBookmarksCommand: function (aSource, aCommand, aArgumentsArray)
  352.   {
  353.     var rCommand = RDF.GetResource(aCommand);
  354.   
  355.     var kSuppArrayContractID = "@mozilla.org/supports-array;1";
  356.     var kSuppArrayIID = Components.interfaces.nsISupportsArray;
  357.     var sourcesArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
  358.     if (aSource) {
  359.       sourcesArray.AppendElement(aSource);
  360.     }
  361.   
  362.     var argsArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
  363.     var length = aArgumentsArray?aArgumentsArray.length:0;
  364.     for (var i = 0; i < length; ++i) {
  365.       var rArc = RDF.GetResource(aArgumentsArray[i].property);
  366.       argsArray.AppendElement(rArc);
  367.       var rValue = null;
  368.       if ("resource" in aArgumentsArray[i]) { 
  369.         rValue = RDF.GetResource(aArgumentsArray[i].resource);
  370.       }
  371.       else
  372.         rValue = RDF.GetLiteral(aArgumentsArray[i].literal);
  373.       argsArray.AppendElement(rValue);
  374.     }
  375.  
  376.     // Exec the command in the Bookmarks datasource. 
  377.     BMDS.DoCommand(sourcesArray, rCommand, argsArray);
  378.   },
  379.  
  380.   undoBookmarkTransaction: function ()
  381.   {
  382.     BMSVC.transactionManager.undoTransaction();
  383.     BookmarksUtils.flushDataSource();
  384.   },
  385.  
  386.   redoBookmarkTransaction: function ()
  387.   {
  388.     BMSVC.transactionManager.redoTransaction();
  389.     BookmarksUtils.flushDataSource();
  390.   },
  391.  
  392.   manageFolder: function (aSelection)
  393.   {
  394.     openDialog("chrome://browser/content/bookmarks/bookmarksManager.xul", 
  395.                "", "chrome,all,dialog=no", aSelection.item[0].Value);
  396.   },
  397.   
  398.   cutBookmark: function (aSelection)
  399.   {
  400.     this.copyBookmark(aSelection);
  401.     BookmarksUtils.removeAndCheckSelection("cut", aSelection);
  402.   },
  403.  
  404.   copyBookmark: function (aSelection)
  405.   {
  406.     const kSuppArrayContractID = "@mozilla.org/supports-array;1";
  407.     const kSuppArrayIID = Components.interfaces.nsISupportsArray;
  408.     var itemArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
  409.  
  410.     const kSuppWStringContractID = "@mozilla.org/supports-string;1";
  411.     const kSuppWStringIID = Components.interfaces.nsISupportsString;
  412.     var bmstring = Components.classes[kSuppWStringContractID].createInstance(kSuppWStringIID);
  413.     var unicodestring = Components.classes[kSuppWStringContractID].createInstance(kSuppWStringIID);
  414.     var htmlstring = Components.classes[kSuppWStringContractID].createInstance(kSuppWStringIID);
  415.   
  416.     var sBookmarkItem = ""; var sTextUnicode = ""; var sTextHTML = "";
  417.     for (var i = 0; i < aSelection.length; ++i) {
  418.       var url  = BookmarksUtils.getProperty(aSelection.item[i], NC_NS+"URL" );
  419.       var name = BookmarksUtils.getProperty(aSelection.item[i], NC_NS+"Name");
  420.       sBookmarkItem += aSelection.item[i].Value + "\n";
  421.       sTextUnicode += url + "\n";
  422.       sTextHTML += "<A HREF=\"" + url + "\">" + name + "</A>";
  423.     }
  424.     
  425.     const kXferableContractID = "@mozilla.org/widget/transferable;1";
  426.     const kXferableIID = Components.interfaces.nsITransferable;
  427.     var xferable = Components.classes[kXferableContractID].createInstance(kXferableIID);
  428.  
  429.     xferable.addDataFlavor("moz/bookmarkclipboarditem");
  430.     bmstring.data = sBookmarkItem;
  431.     xferable.setTransferData("moz/bookmarkclipboarditem", bmstring, sBookmarkItem.length*2);
  432.     
  433.     xferable.addDataFlavor("text/html");
  434.     htmlstring.data = sTextHTML;
  435.     xferable.setTransferData("text/html", htmlstring, sTextHTML.length*2);
  436.     
  437.     xferable.addDataFlavor("text/unicode");
  438.     unicodestring.data = sTextUnicode;
  439.     xferable.setTransferData("text/unicode", unicodestring, sTextUnicode.length*2);
  440.     
  441.     const kClipboardContractID = "@mozilla.org/widget/clipboard;1";
  442.     const kClipboardIID = Components.interfaces.nsIClipboard;
  443.     var clipboard = Components.classes[kClipboardContractID].getService(kClipboardIID);
  444.     clipboard.setData(xferable, null, kClipboardIID.kGlobalClipboard);
  445.   },
  446.  
  447.   pasteBookmark: function (aTarget)
  448.   {
  449.     const kXferableContractID = "@mozilla.org/widget/transferable;1";
  450.     const kXferableIID = Components.interfaces.nsITransferable;
  451.     var xferable = Components.classes[kXferableContractID].createInstance(kXferableIID);
  452.     xferable.addDataFlavor("moz/bookmarkclipboarditem");
  453.     xferable.addDataFlavor("text/x-moz-url");
  454.     xferable.addDataFlavor("text/unicode");
  455.  
  456.     const kClipboardContractID = "@mozilla.org/widget/clipboard;1";
  457.     const kClipboardIID = Components.interfaces.nsIClipboard;
  458.     var clipboard = Components.classes[kClipboardContractID].getService(kClipboardIID);
  459.     clipboard.getData(xferable, kClipboardIID.kGlobalClipboard);
  460.     
  461.     var flavour = { };
  462.     var data    = { };
  463.     var length  = { };
  464.     xferable.getAnyTransferData(flavour, data, length);
  465.     var items, name, url;
  466.     data = data.value.QueryInterface(Components.interfaces.nsISupportsString).data;
  467.     switch (flavour.value) {
  468.     case "moz/bookmarkclipboarditem":
  469.       items = data.split("\n");
  470.       // since data are ended by \n, remove the last empty node
  471.       items.pop(); 
  472.       for (var i=0; i<items.length; ++i) {
  473.         items[i] = RDF.GetResource(items[i]);
  474.       }
  475.       break;
  476.     case "text/x-moz-url":
  477.       // there should be only one item in this case
  478.       var ix = data.indexOf("\n");
  479.       items = data.substring(0, ix != -1 ? ix : data.length);
  480.       name  = data.substring(ix);
  481.       // XXX: we should infer the best charset
  482.       BookmarksUtils.createBookmark(null, items, null, name, null);
  483.       items = [items];
  484.       break;
  485.     default: 
  486.       return;
  487.     }
  488.    
  489.     var selection = {item: items, parent:Array(items.length), length: items.length};
  490.     BookmarksUtils.checkSelection(selection);
  491.     BookmarksUtils.insertAndCheckSelection("paste", selection, aTarget, -1);
  492.   },
  493.   
  494.   deleteBookmark: function (aSelection)
  495.   {
  496.     // call checkSelection here to update the immutable and other
  497.     // flags on the selection; when new resources get created,
  498.     // they're temporarily not valid because they're not in a
  499.     // bookmark container.  So, they can't be removed until that's
  500.     // fixed.
  501.     BookmarksUtils.checkSelection(aSelection);
  502.     BookmarksUtils.removeAndCheckSelection("delete", aSelection);
  503.   },
  504.  
  505.   moveBookmark: function (aSelection)
  506.   {
  507.     var rv = { selectedFolder: null };      
  508.     openDialog("chrome://browser/content/bookmarks/addBookmark.xul", "", 
  509.                "centerscreen,chrome,modal=yes,dialog=yes,resizable=yes", null, null, null, null, "selectFolder", rv);
  510.     if (!rv.target)
  511.       return;
  512.     BookmarksUtils.moveAndCheckSelection("move", aSelection, rv.target);
  513.   },
  514.  
  515.   openBookmark: function (aSelection, aTargetBrowser, aDS) 
  516.   {
  517.     if (!aTargetBrowser)
  518.       return;
  519.     for (var i=0; i<aSelection.length; ++i) {
  520.       var type = aSelection.type[i];
  521.       if (aTargetBrowser == "save") {
  522.         var item = aSelection.item[i];
  523.         saveURL(item.Value, BookmarksUtils.getProperty(item, "Name"), null, true);
  524.       }
  525.       else if (type == "Bookmark" || type == "ImmutableBookmark") {
  526.         var webPanel = BMDS.GetTarget(aSelection.item[i],
  527.                                       RDF.GetResource(NC_NS + "WebPanel"),
  528.                                       true);
  529.         if (webPanel && aTargetBrowser == "current")
  530.           this.openWebPanel(aSelection.item[i].Value, aDS);
  531.         else
  532.           this.openOneBookmark(aSelection.item[i].Value, aTargetBrowser, aDS);
  533.       }
  534.       else if (type == "Folder" || type == "PersonalToolbarFolder" || type == "Livemark")
  535.         this.openGroupBookmark(aSelection.item[i].Value, aTargetBrowser);
  536.     }
  537.   },
  538.   
  539.   openBookmarkProperties: function (aSelection) 
  540.   {
  541.     // Bookmark Properties dialog is only ever opened with one selection 
  542.     // (command is disabled otherwise)
  543.     var bookmark = aSelection.item[0].Value;
  544.     value = {};
  545.     openDialog("chrome://browser/content/bookmarks/bookmarksProperties.xul", "", "centerscreen,chrome,modal,resizable=no", bookmark, value);
  546.     return value.ok;
  547.   },
  548.  
  549.   // requires utilityOverlay.js if opening in new window for getTopWin()
  550.   openWebPanel: function(aResource, aDS)
  551.   {
  552.     var url = BookmarksUtils.getProperty(aResource, NC_NS+"URL", aDS);
  553.     // Ignore "NC:" and empty urls.
  554.     if (url == "")
  555.       return;
  556.     var w = getTopWin();
  557.     if (!w) {
  558.       openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", url);
  559.       return;
  560.     }
  561.     w.openWebPanel(BookmarksUtils.getProperty(aResource,  NC_NS+"Name"), url);
  562.   },
  563.   
  564.   // requires utilityOverlay.js because it calls openUILinkIn
  565.   openOneBookmark: function(aURI, aTargetBrowser, aDS)
  566.   {
  567.     var url = BookmarksUtils.getProperty(aURI, NC_NS+"URL", aDS);
  568.     // Ignore "NC:" and empty urls.
  569.     if (url == "")
  570.       return;
  571.     var w = aTargetBrowser == "window"? null:getTopWin();
  572.     if (!w) {
  573.       openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", url);
  574.       return;
  575.     }
  576.     var browser = w.document.getElementById("content");
  577.     switch (aTargetBrowser) {
  578.     case "current":
  579.       var loadInBackground = false;
  580.       loadInBackground = PREF.getBoolPref("browser.tabs.loadInBackground");
  581.       var bookmarkOpen = PREF.getCharPref("browser.tabs.bookmark.open");
  582.  
  583.       // (ccampbell): special case for javascript booklets to open in same tab
  584.       if (url.indexOf('javascript:')==0) bookmarkOpen = 'overwrite';
  585.  
  586.       if (bookmarkOpen == 'overwrite') {
  587.         browser.loadURI(url);
  588.         w._content.focus();
  589.       } else { // == 'new'
  590.         // open link in new tab
  591.         var tab = browser.addTabAt(url);
  592.   
  593.         if (!loadInBackground)
  594.           browser.selectedTab = tab;
  595.   
  596.         browser.focus();
  597.       }
  598.       break;
  599.     case "tab":
  600.       var loadInBackground = false;
  601.       loadInBackground = PREF.getBoolPref("browser.tabs.loadInBackground");
  602.  
  603.       // open link in new tab
  604.       var tab = browser.addTabAt(url);
  605.  
  606.       if (!loadInBackground)
  607.         browser.selectedTab = tab;
  608.  
  609.       browser.focus();
  610.  
  611.       break;
  612.     }
  613.   },
  614.  
  615.   openGroupBookmark: function (aURI, aTargetBrowser)
  616.   {
  617.     if (aTargetBrowser == "current" || aTargetBrowser == "tab") {
  618.       var w        = getTopWin();
  619.       var browser  = w.document.getElementById("content");
  620.       var resource = RDF.GetResource(aURI);
  621.       var urlArc   = RDF.GetResource(NC_NS+"URL");
  622.       RDFC.Init(BMDS, resource);
  623.       var containerChildren = RDFC.GetElements();
  624.       var tabPanels = browser.browsers;
  625.       var tabCount  = tabPanels.length;
  626.       //var doReplace = PREF.getBoolPref("browser.tabs.loadFolderAndReplace");
  627.       var bookmarkOpen = PREF.getCharPref("browser.tabs.bookmark.open");
  628.       var loadInBackground = PREF.getBoolPref("browser.tabs.loadBookmarksInBackground");
  629.       // MERC (DP): build the URL string
  630.       var count = 0;
  631.       var uriString;
  632.       var uri = "";
  633.       while(containerChildren.hasMoreElements()) {
  634.         var res = containerChildren.getNext().QueryInterface(kRDFRSCIID);
  635.         var target = BMDS.GetTarget(res, urlArc, true);
  636.         if (target) {
  637.           if(count) {
  638.             uriString += '|' + target.QueryInterface(kRDFLITIID).Value;
  639.           } else { // first time
  640.             uriString = target.QueryInterface(kRDFLITIID).Value;
  641.           }
  642.         }
  643.         count++;
  644.       }
  645.       dump('***URL string: ' + uriString + '\n');
  646.  
  647.       // if overwrite: load tabs left to right - add tabs as needed
  648.       // if new: add tabs starting at position specified browser.tabs.newTabLocation
  649.       if(bookmarkOpen == 'overwrite') {
  650.         w.loadOneOrMoreURIsOnHomePageClick(uriString);
  651.       } else { // else == 'new'
  652.         w.addOneOrMoreURIs(uriString);
  653.       }
  654.       /* MERC (DP): if there is a blank tab at end, start replacing there.
  655.       we will take out this code for consistency with other behaviour e.g. home pages
  656.       var index0;
  657.       if (doReplace)
  658.         index0 = 0;
  659.       else {
  660.         for (index0=tabCount-1; index0>=0; --index0)
  661.           if (browser.browsers[index0].webNavigation.currentURI.spec != "about:blank")
  662.             break;
  663.         ++index0;
  664.       }
  665.  
  666.       var index  = index0;
  667.       while (containerChildren.hasMoreElements()) {
  668.         var res = containerChildren.getNext().QueryInterface(kRDFRSCIID);
  669.         var target = BMDS.GetTarget(res, urlArc, true);
  670.         if (target) {
  671.           var uri = target.QueryInterface(kRDFLITIID).Value;
  672.           if (index < tabCount)
  673.             tabPanels[index].loadURI(uri);
  674.           else
  675.             browser.addTab(uri);
  676.           ++index;
  677.         }
  678.       }
  679.  
  680.       // If the bookmark group was completely invalid, just bail.
  681.       if (index == index0)
  682.         return;
  683.  
  684.       // focus the first tab if prefs say to
  685.       if (!loadInBackground || doReplace) {
  686.         // Select the first tab in the group.
  687.         var tabs = browser.mTabContainer.childNodes;
  688.         browser.selectedTab = tabs[index0];
  689.       }
  690.  
  691.       // Close any remaining open tabs that are left over.
  692.       // (Always skipped when we append tabs)
  693.       for (var i = tabCount-1; i >= index; --i)
  694.         browser.removeTab(tabs[i]);
  695.  
  696.       // and focus the content
  697.       w._content.focus();
  698.       */
  699.     } else {
  700.       dump("Open Group in new window: not implemented...\n");
  701.     }
  702.   },
  703.  
  704.   createNewBookmark: function (aTarget)
  705.   {
  706.     var name     = BookmarksUtils.getLocaleString("ile_newbookmark");
  707.     var resource = BMSVC.createBookmark(name, "", "", "", "", null);
  708.     this.createNewResource(resource, aTarget, "newbookmark");
  709.   },
  710.  
  711.   createNewLivemark: function (aTarget)
  712.   {
  713.     var name     = BookmarksUtils.getLocaleString("ile_newlivemark");
  714.     var resource = BMSVC.createLivemark(name, "", "", null);
  715.     this.createNewResource(resource, aTarget, "newlivemark");
  716.   },
  717.  
  718.   createNewFolder: function (aTarget)
  719.   {
  720.     var name     = BookmarksUtils.getLocaleString("ile_newfolder");
  721.     var resource = BMSVC.createFolder(name);
  722.     this.createNewResource(resource, aTarget, "newfolder");
  723.     // temporary hack...
  724.     return resource;
  725.   },
  726.  
  727.   createNewSeparator: function (aTarget)
  728.   {
  729.     var resource = BMSVC.createSeparator();
  730.     this.createNewResource(resource, aTarget, "newseparator");
  731.   },
  732.  
  733.   createNewResource: function(aResource, aTarget, aTxnType)
  734.   {
  735.     var selection = BookmarksUtils.getSelectionFromResource(aResource, aTarget.parent);
  736.     var ok        = BookmarksUtils.insertAndCheckSelection(aTxnType, selection, aTarget, -1);
  737.     if (ok && aTxnType != "newseparator") {
  738.       ok = this.openBookmarkProperties(selection);
  739.       if (!ok)
  740.         BookmarksCommand.deleteBookmark(selection);
  741.     }
  742.   },
  743.  
  744.   importBookmarks: function ()
  745.   {
  746.       // XXX: ifdef it to be non-modal (non-"sheet") on mac (see bug 259039)
  747.       var features = "modal,centerscreen,chrome,resizable=no";
  748.       window.fromFile = false;
  749.       window.openDialog("chrome://browser/content/migration/migration.xul", "migration", features, "bookmarks");
  750.       if(window.fromFile)
  751.       {
  752.         this.importBookmarksFromFile();
  753.       }
  754.   },
  755.  
  756.   importBookmarksFromFile: function ()
  757.   {
  758.     ///transaction...
  759.     try {
  760.       const kFilePickerContractID = "@mozilla.org/filepicker;1";
  761.       const kFilePickerIID = Components.interfaces.nsIFilePicker;
  762.       const kFilePicker = Components.classes[kFilePickerContractID].createInstance(kFilePickerIID);
  763.     
  764.       const kTitle = BookmarksUtils.getLocaleString("SelectImport");
  765.       kFilePicker.init(window, kTitle, kFilePickerIID["modeOpen"]);
  766.       kFilePicker.appendFilters(kFilePickerIID.filterHTML | kFilePickerIID.filterAll);
  767.       var fileName;
  768.       if (kFilePicker.show() != kFilePickerIID.returnCancel) {
  769.         fileName = kFilePicker.file.path;
  770.         if (!fileName) return;
  771.       }
  772.       else return;
  773.     }
  774.     catch (e) {
  775.       return;
  776.     }
  777.     rTarget = RDF.GetResource("NC:BookmarksRoot");
  778.     RDFC.Init(BMDS, rTarget);
  779.     var countBefore = parseInt(BookmarksUtils.getProperty(rTarget, RDF_NS+"nextVal"));
  780.     var args = [{ property: NC_NS+"URL", literal: fileName}];
  781.     this.doBookmarksCommand(rTarget, NC_NS_CMD+"import", args);
  782.     var countAfter = parseInt(BookmarksUtils.getProperty(rTarget, RDF_NS+"nextVal"));
  783.  
  784.     var transaction = new BookmarkImportTransaction("import");
  785.     for (var index = countBefore; index < countAfter; index++) {
  786.       var nChildArc = RDFCU.IndexToOrdinalResource(index);
  787.       var rChild    = BMDS.GetTarget(rTarget, nChildArc, true);
  788.       transaction.item   .push(rChild);
  789.       transaction.parent .push(rTarget);
  790.       transaction.index  .push(index);
  791.     }
  792.     BMSVC.transactionManager.doTransaction(transaction);
  793.     BookmarksUtils.flushDataSource();
  794.   },
  795.  
  796.   exportBookmarks: function ()
  797.   {
  798.     try {
  799.       const kFilePickerContractID = "@mozilla.org/filepicker;1";
  800.       const kFilePickerIID = Components.interfaces.nsIFilePicker;
  801.       const kFilePicker = Components.classes[kFilePickerContractID].createInstance(kFilePickerIID);
  802.       
  803.       const kTitle = BookmarksUtils.getLocaleString("EnterExport");
  804.       kFilePicker.init(window, kTitle, kFilePickerIID["modeSave"]);
  805.       kFilePicker.appendFilters(kFilePickerIID.filterHTML | kFilePickerIID.filterAll);
  806.       kFilePicker.defaultString = "bookmarks.html";
  807.       var fileName;
  808.       if (kFilePicker.show() != kFilePickerIID.returnCancel) {
  809.         fileName = kFilePicker.file.path;
  810.         if (!fileName) return;
  811.       }
  812.       else return;
  813.  
  814.       var file = Components.classes["@mozilla.org/file/local;1"]
  815.                            .createInstance(Components.interfaces.nsILocalFile);
  816.       if (!file)
  817.         return;
  818.       file.initWithPath(fileName);
  819.       if (!file.exists()) {
  820.         file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
  821.       }
  822.     }
  823.     catch (e) {
  824.       return;
  825.     }
  826.     var selection = RDF.GetResource("NC:BookmarksRoot");
  827.     var args = [{ property: NC_NS+"URL", literal: fileName}];
  828.     this.doBookmarksCommand(selection, NC_NS_CMD+"export", args);
  829.   },
  830.  
  831.   refreshLivemark: function (aSelection)
  832.   {
  833.     var exp = RDF.GetResource(NC_NS+"LivemarkExpiration");
  834.     for (var i = 0; i < aSelection.length; i++) {
  835.       rsrc = RDF.GetResource(aSelection.item[i].Value);
  836.       oldtgt = BMDS.GetTarget(rsrc, exp, true);
  837.       if (oldtgt) {
  838.         BMDS.Unassert(rsrc, exp, oldtgt);
  839.       }
  840.     }
  841.   },
  842.  
  843.   sortByName: function (aSelection)
  844.   {
  845.     // do the real sorting in a timeout, to make sure that
  846.     // if we sort from a menu that the menu gets torn down
  847.     // before we sort.  the template builder really doesn't
  848.     // like it if we move things around; the menu code also
  849.     // doesn't like it if we move the menuparent while a
  850.     // popup is open.
  851.     setTimeout(function () { BookmarksCommand.realSortByName(aSelection); }, 0);
  852.   },
  853.  
  854.   realSortByName: function (aSelection)
  855.   {
  856.     var theFolder;
  857.  
  858.     if (aSelection.length != 1)
  859.       return;
  860.  
  861.     var selType = BookmarksUtils.resolveType (aSelection.item[0]);
  862.     if (selType == "Folder" || selType == "Bookmark" ||
  863.         selType == "PersonalToolbarFolder" || selType == "Livemark")
  864.     {
  865.       theFolder = aSelection.parent[0];
  866.     } else {
  867.       // we're not going to try to sort ImmutableBookmark siblings or
  868.       // any other such thing, since it'll probably just get us into
  869.       // trouble
  870.       return;
  871.     }
  872.  
  873.     var toSort = [];
  874.     RDFC.Init(BMDS, theFolder);
  875.     var folderContents = RDFC.GetElements();
  876.     while (folderContents.hasMoreElements()) {
  877.         var rsrc = folderContents.getNext().QueryInterface(kRDFRSCIID);
  878.         var rtype = BookmarksUtils.resolveType(rsrc);
  879.         if (rtype == "BookmarkSeparator")
  880.           continue;
  881.         toSort.push(rsrc);
  882.     }
  883.  
  884.     const kName = RDF.GetResource(NC_NS+"Name");
  885.  
  886.     var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
  887.                                   .getService(Components.interfaces.nsILocaleService);
  888.     var collationFactory = Components.classes["@mozilla.org/intl/collation-factory;1"]
  889.                                      .getService(Components.interfaces.nsICollationFactory);
  890.     var collation = collationFactory.CreateCollation(localeService.getApplicationLocale());
  891.  
  892.     toSort.sort (function (a, b) {
  893.                    var atype = BookmarksUtils.resolveType(a);
  894.                    var btype = BookmarksUtils.resolveType(b);
  895.  
  896.                    var aisfolder = (atype == "Folder") || (atype == "PersonalToolbarFolder");
  897.                    var bisfolder = (btype == "Folder") || (btype == "PersonalToolbarFolder");
  898.  
  899.                    // folders above bookmarks
  900.                    if (aisfolder && !bisfolder)
  901.                      return -1;
  902.                    if (bisfolder && !aisfolder)
  903.                      return 1;
  904.  
  905.                    // then sort by name
  906.                    var aname = BMDS.GetTarget(a, kName, true).QueryInterface(kRDFLITIID).Value;
  907.                    var bname = BMDS.GetTarget(b, kName, true).QueryInterface(kRDFLITIID).Value;
  908.  
  909.                    return collation.compareString(0, aname, bname);
  910.                  });
  911.  
  912.     // we now have the resources here sorted by name
  913.     BMDS.beginUpdateBatch();
  914.  
  915.     RDFC.Init(BMDS, theFolder);
  916.  
  917.     // remove existing elements
  918.     var folderContents = RDFC.GetElements();
  919.     while (folderContents.hasMoreElements()) {
  920.       RDFC.RemoveElement (folderContents.getNext(), false);
  921.     }
  922.  
  923.     // and add our elements back
  924.     for (var i = 0; i < toSort.length; i++) {
  925.       RDFC.InsertElementAt (toSort[i], i+1, true);
  926.     }
  927.  
  928.     BMDS.endUpdateBatch();
  929.   }
  930.  
  931. }
  932.  
  933.   /////////////////////////////////////////////////////////////////////////////
  934.   // Command handling & Updating.
  935. var BookmarksController = {
  936.  
  937.   supportsCommand: function (aCommand)
  938.   {
  939.     var isCommandSupported;
  940.     switch(aCommand) {
  941.     case "cmd_undo":
  942.     case "cmd_redo":
  943.     case "cmd_bm_undo":
  944.     case "cmd_bm_redo":
  945.     case "cmd_cut":
  946.     case "cmd_copy":
  947.     case "cmd_paste":
  948.     case "cmd_delete":
  949.     case "cmd_selectAll":
  950.     case "cmd_bm_open":
  951.     case "cmd_bm_openinnewwindow":
  952.     case "cmd_bm_openinnewtab":
  953.     case "cmd_bm_expandfolder":
  954.     case "cmd_bm_openfolder":
  955.     case "cmd_bm_managefolder":
  956.     case "cmd_bm_newbookmark":
  957.     case "cmd_bm_newlivemark":
  958.     case "cmd_bm_newfolder":
  959.     case "cmd_bm_newseparator":
  960.     case "cmd_bm_properties":
  961.     case "cmd_bm_rename":
  962.     case "cmd_bm_setnewbookmarkfolder":
  963.     case "cmd_bm_setpersonaltoolbarfolder":
  964.     case "cmd_bm_setnewsearchfolder":
  965.     case "cmd_bm_import":
  966.     case "cmd_bm_export":
  967.     case "cmd_bm_movebookmark":
  968.     case "cmd_bm_refreshlivemark":
  969.     case "cmd_bm_sortbyname":
  970.       isCommandSupported = true;
  971.       break;
  972.     default:
  973.       isCommandSupported = false;
  974.     }
  975.     //if (!isCommandSupported)
  976.     //  dump("Bookmark command '"+aCommand+"' is not supported!\n");
  977.     return isCommandSupported;
  978.   },
  979.  
  980.   isCommandEnabled: function (aCommand, aSelection, aTarget)
  981.   {
  982.     var item0, type0, junk;
  983.     var length = 0;
  984.     if (aSelection && aSelection.length != 0) {
  985.       length = aSelection.length;
  986.       item0 = aSelection.item[0].Value;
  987.       type0 = aSelection.type[0];
  988.     }
  989.     var i;
  990.  
  991.     switch(aCommand) {
  992.     case "cmd_undo":
  993.     case "cmd_bm_undo":
  994.       return BMSVC.transactionManager.numberOfUndoItems > 0;
  995.     case "cmd_redo":
  996.     case "cmd_bm_redo":
  997.       return BMSVC.transactionManager.numberOfRedoItems > 0;
  998.     case "cmd_paste":
  999.       if (aTarget && !BookmarksUtils.isValidTargetContainer(aTarget.parent))
  1000.         return false;
  1001.       const kClipboardContractID = "@mozilla.org/widget/clipboard;1";
  1002.       const kClipboardIID = Components.interfaces.nsIClipboard;
  1003.       var clipboard = Components.classes[kClipboardContractID].getService(kClipboardIID);
  1004.       const kSuppArrayContractID = "@mozilla.org/supports-array;1";
  1005.       const kSuppArrayIID = Components.interfaces.nsISupportsArray;
  1006.       var flavourArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
  1007.       const kSuppStringContractID = "@mozilla.org/supports-cstring;1";
  1008.       const kSuppStringIID = Components.interfaces.nsISupportsCString;
  1009.     
  1010.       var flavours = ["moz/bookmarkclipboarditem", "text/x-moz-url"];
  1011.       for (i = 0; i < flavours.length; ++i) {
  1012.         const kSuppString = Components.classes[kSuppStringContractID].createInstance(kSuppStringIID);
  1013.         kSuppString.data = flavours[i];
  1014.         flavourArray.AppendElement(kSuppString);
  1015.       }
  1016.       var hasFlavours = clipboard.hasDataMatchingFlavors(flavourArray, kClipboardIID.kGlobalClipboard);
  1017.       return hasFlavours;
  1018.     case "cmd_copy":
  1019.       return length > 0;
  1020.     case "cmd_cut":
  1021.     case "cmd_delete":
  1022.       return length > 0 && !aSelection.containsImmutable && !aSelection.containsPTF;
  1023.     case "cmd_selectAll":
  1024.       return true;
  1025.     case "cmd_bm_open":
  1026.     case "cmd_bm_expandfolder":
  1027.     case "cmd_bm_managefolder":
  1028.       return length == 1;
  1029.     case "cmd_bm_openinnewwindow":
  1030.     case "cmd_bm_openinnewtab":
  1031.       return true;
  1032.     case "cmd_bm_openfolder":
  1033.       for (i=0; i<length; ++i) {
  1034.         if (aSelection.type[i] == "ImmutableBookmark" ||
  1035.             aSelection.type[i] == "ImmutableFolder" ||
  1036.             aSelection.type[i] == "Bookmark" ||
  1037.             aSelection.type[i] == "BookmarkSeparator")
  1038.           return false;
  1039.         RDFC.Init(BMDS, aSelection.item[i]);
  1040.         var children = RDFC.GetElements();
  1041.         while (children.hasMoreElements()) {
  1042.           var childType = BookmarksUtils.resolveType(children.getNext());
  1043.           if (childType == "Bookmark" || childType == "LivemarkBookmark")
  1044.             return true;
  1045.         }
  1046.       }
  1047.       return false;
  1048.     case "cmd_bm_import":
  1049.     case "cmd_bm_export":
  1050.       return true;
  1051.     case "cmd_bm_newbookmark":
  1052.     case "cmd_bm_newlivemark":
  1053.     case "cmd_bm_newfolder":
  1054.     case "cmd_bm_newseparator":
  1055.       return ((type0 == "PersonalToolbarFolder") ||
  1056.               (aTarget && BookmarksUtils.isValidTargetContainer(aTarget.parent)));
  1057.     case "cmd_bm_properties":
  1058.     case "cmd_bm_rename":
  1059.       if (length != 1 ||
  1060.           aSelection.item[0].Value == "NC:BookmarksRoot" ||
  1061.           BookmarksUtils.resolveType(aSelection.parent[0]) == "Livemark")
  1062.         return false;
  1063.       return true;
  1064.     case "cmd_bm_setpersonaltoolbarfolder":
  1065.       if (length != 1 || type0 == "Livemark")
  1066.         return false;
  1067.       return item0 != BMSVC.getBookmarksToolbarFolder().Value && 
  1068.              item0 != "NC:BookmarksRoot" && type0 == "Folder";
  1069.     case "cmd_bm_movebookmark":
  1070.       return length > 0 && !aSelection.containsImmutable;
  1071.     case "cmd_bm_refreshlivemark":
  1072.       for (i=0; i<length; ++i) {
  1073.         if (aSelection.type[i] != "Livemark")
  1074.           return false;
  1075.       }
  1076.       return length > 0;
  1077.     case "cmd_bm_sortbyname":
  1078.       if (length == 1 && (aSelection.type[0] == "Folder" ||
  1079.                           aSelection.type[0] == "Bookmark" ||
  1080.                           aSelection.type[0] == "PersonalToolbarFolder" ||
  1081.                           aSelection.type[0] == "Livemark"))
  1082.         return true;
  1083.       return false;
  1084.     default:
  1085.       return false;
  1086.     }
  1087.   },
  1088.  
  1089.   doCommand: function (aCommand, aSelection, aTarget, aDS)
  1090.   {
  1091.     var resource0, type0, realTarget;
  1092.     if (aSelection && aSelection.length == 1) {
  1093.       resource0 = aSelection.item[0];
  1094.       type0 = aSelection.type[0];
  1095.     }
  1096.  
  1097.     if (type0 == "PersonalToolbarFolder" && aTarget == null)
  1098.       realTarget = { parent: resource0, index: -1 };
  1099.     else
  1100.       realTarget = aTarget;
  1101.  
  1102.     switch (aCommand) {
  1103.     case "cmd_undo":
  1104.     case "cmd_bm_undo":
  1105.       BookmarksCommand.undoBookmarkTransaction();
  1106.       break;
  1107.     case "cmd_redo":
  1108.     case "cmd_bm_redo":
  1109.       BookmarksCommand.redoBookmarkTransaction();
  1110.       break;
  1111.     case "cmd_bm_open":
  1112.       BookmarksCommand.openBookmark(aSelection, "current", aDS);
  1113.       break;
  1114.     case "cmd_bm_openinnewwindow":
  1115.       BookmarksCommand.openBookmark(aSelection, "window", aDS);
  1116.       break;
  1117.     case "cmd_bm_openinnewtab":
  1118.       BookmarksCommand.openBookmark(aSelection, "tab", aDS);
  1119.       break;
  1120.     case "cmd_bm_openfolder":
  1121.       BookmarksCommand.openBookmark(aSelection, "current", aDS);
  1122.       break;
  1123.     case "cmd_bm_managefolder":
  1124.       BookmarksCommand.manageFolder(aSelection);
  1125.       break;
  1126.     case "cmd_bm_setnewbookmarkfolder":
  1127.     case "cmd_bm_setpersonaltoolbarfolder":
  1128.     case "cmd_bm_setnewsearchfolder":
  1129.       BookmarksCommand.doBookmarksCommand(aSelection.item[0], NC_NS_CMD+aCommand.substring("cmd_bm_".length), []);
  1130.       break;
  1131.     case "cmd_bm_rename":
  1132.     case "cmd_bm_properties":
  1133.       junk = BookmarksCommand.openBookmarkProperties(aSelection);
  1134.       break;
  1135.     case "cmd_cut":
  1136.       BookmarksCommand.cutBookmark(aSelection);
  1137.       break;
  1138.     case "cmd_copy":
  1139.       BookmarksCommand.copyBookmark(aSelection);
  1140.       break;
  1141.     case "cmd_paste":
  1142.       BookmarksCommand.pasteBookmark(realTarget);
  1143.       break;
  1144.     case "cmd_delete":
  1145.       BookmarksCommand.deleteBookmark(aSelection);
  1146.       break;
  1147.     case "cmd_bm_movebookmark":
  1148.       BookmarksCommand.moveBookmark(aSelection);
  1149.       break;
  1150.     case "cmd_bm_newbookmark":
  1151.       BookmarksCommand.createNewBookmark(realTarget);
  1152.       break;
  1153.     case "cmd_bm_newlivemark":
  1154.       BookmarksCommand.createNewLivemark(realTarget);
  1155.       break;
  1156.     case "cmd_bm_newfolder":
  1157.       BookmarksCommand.createNewFolder(realTarget);
  1158.       break;
  1159.     case "cmd_bm_newseparator":
  1160.       BookmarksCommand.createNewSeparator(realTarget);
  1161.       break;
  1162.     case "cmd_bm_import":
  1163.       BookmarksCommand.importBookmarks();
  1164.       break;
  1165.     case "cmd_bm_export":
  1166.       BookmarksCommand.exportBookmarks();
  1167.       break;
  1168.     case "cmd_bm_refreshlivemark":
  1169.       BookmarksCommand.refreshLivemark(aSelection);
  1170.       break;
  1171.     case "cmd_bm_sortbyname":
  1172.       BookmarksCommand.sortByName(aSelection);
  1173.       break;
  1174.     default: 
  1175.       dump("Bookmark command "+aCommand+" not handled!\n");
  1176.     }
  1177.  
  1178.   },
  1179.  
  1180.   onCommandUpdate: function (aSelection, aTarget)
  1181.   {
  1182.     var commands = ["cmd_bm_newbookmark", "cmd_bm_newlivemark", "cmd_bm_newfolder", "cmd_bm_newseparator",
  1183.                     "cmd_undo", "cmd_redo", "cmd_bm_properties", "cmd_bm_rename", 
  1184.                     "cmd_copy", "cmd_paste", "cmd_cut", "cmd_delete",
  1185.                     "cmd_bm_setpersonaltoolbarfolder", "cmd_bm_movebookmark",
  1186.                     "cmd_bm_openfolder", "cmd_bm_managefolder", "cmd_bm_refreshlivemark", "cmd_bm_sortbyname"];
  1187.     for (var i = 0; i < commands.length; ++i) {
  1188.       var enabled = this.isCommandEnabled(commands[i], aSelection, aTarget);
  1189.       var commandNode = document.getElementById(commands[i]);
  1190.      if (commandNode) { 
  1191.         if (enabled) 
  1192.           commandNode.removeAttribute("disabled");
  1193.         else 
  1194.           commandNode.setAttribute("disabled", "true");
  1195.       }
  1196.     }
  1197.   }
  1198. }
  1199.  
  1200. function CommandArrayEnumerator (aCommandArray)
  1201. {
  1202.   this._inner = [];
  1203.   for (var i = 0; i < aCommandArray.length; ++i)
  1204.     this._inner.push(RDF.GetResource(NC_NS_CMD + aCommandArray[i]));
  1205.     
  1206.   this._index = 0;
  1207. }
  1208.  
  1209. CommandArrayEnumerator.prototype = {
  1210.   getNext: function () 
  1211.   {
  1212.     return this._inner[this._index];
  1213.   },
  1214.   
  1215.   hasMoreElements: function ()
  1216.   {
  1217.     return this._index < this._inner.length;
  1218.   }
  1219. };
  1220.  
  1221. var BookmarksUtils = {
  1222.  
  1223.   DROP_BEFORE: Components.interfaces.nsITreeView.inDropBefore,
  1224.   DROP_ON    : Components.interfaces.nsITreeView.inDropOn,
  1225.   DROP_AFTER : Components.interfaces.nsITreeView.inDropAfter,
  1226.  
  1227.   _bundle        : null,
  1228.   _brandShortName: null,
  1229.  
  1230.   /////////////////////////////////////////////////////////////////////////////////////
  1231.   // returns a property from chrome://browser/locale/bookmarks/bookmarks.properties
  1232.   getLocaleString: function (aStringKey, aReplaceString)
  1233.   {
  1234.     if (!this._bundle) {
  1235.       // for those who would xblify Bookmarks.js, there is a need to create string bundle 
  1236.       // manually instead of using <xul:stringbundle/> see bug 63370 for details
  1237.       var LOCALESVC = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
  1238.                                 .getService(Components.interfaces.nsILocaleService);
  1239.       var BUNDLESVC = Components.classes["@mozilla.org/intl/stringbundle;1"]
  1240.                                 .getService(Components.interfaces.nsIStringBundleService);
  1241.       var bookmarksBundle  = "chrome://browser/locale/bookmarks/bookmarks.properties";
  1242.       this._bundle         = BUNDLESVC.createBundle(bookmarksBundle, LOCALESVC.getApplicationLocale());
  1243.       var brandBundle      = "chrome://global/locale/brand.properties";
  1244.       this._brandShortName = BUNDLESVC.createBundle(brandBundle,     LOCALESVC.getApplicationLocale())
  1245.                                       .GetStringFromName("brandShortName");
  1246.     }
  1247.    
  1248.     var bundle;
  1249.     try {
  1250.       if (!aReplaceString)
  1251.         bundle = this._bundle.GetStringFromName(aStringKey);
  1252.       else if (typeof(aReplaceString) == "string")
  1253.         bundle = this._bundle.formatStringFromName(aStringKey, [aReplaceString], 1);
  1254.       else
  1255.         bundle = this._bundle.formatStringFromName(aStringKey, aReplaceString, aReplaceString.length);
  1256.     } catch (e) {
  1257.       dump("Bookmark bundle "+aStringKey+" not found!\n");
  1258.       bundle = "";
  1259.     }
  1260.  
  1261.     bundle = bundle.replace(/%brandShortName%/, this._brandShortName);
  1262.     return bundle;
  1263.   },
  1264.     
  1265.   /////////////////////////////////////////////////////////////////////////////
  1266.   // returns the literal targeted by the URI aArcURI for a resource or uri
  1267.   getProperty: function (aInput, aArcURI, aDS)
  1268.   {
  1269.     var node;
  1270.     var arc  = RDF.GetResource(aArcURI);
  1271.     if (typeof(aInput) == "string") 
  1272.       aInput = RDF.GetResource(aInput);
  1273.     if (!aDS)
  1274.       node = BMDS.GetTarget(aInput, arc, true);
  1275.     else
  1276.       node = aDS .GetTarget(aInput, arc, true);
  1277.     try {
  1278.       return node.QueryInterface(kRDFRSCIID).Value;
  1279.     }
  1280.     catch (e) {
  1281.       return node? node.QueryInterface(kRDFLITIID).Value : "";
  1282.     }    
  1283.   },
  1284.  
  1285.   /////////////////////////////////////////////////////////////////////////////
  1286.   // Determine the rdf:type property for the given resource.
  1287.   resolveType: function (aResource, aDS)
  1288.   {
  1289.     var type = this.getProperty(aResource, RDF_NS+"type", aDS);
  1290.     if (type != "")
  1291.       type = type.split("#")[1];
  1292.     if (type == "Folder") {
  1293.       if (aResource == BMSVC.getBookmarksToolbarFolder())
  1294.         type = "PersonalToolbarFolder";
  1295.     }
  1296.  
  1297.     if (type == "") {
  1298.       // we're not sure what type it is.  figure out if it's a container.
  1299.       var child = this.getProperty(aResource, NC_NS+"child", aDS);
  1300.       if (child || RDFCU.IsContainer(aDS?aDS:BMDS, RDF.GetResource(aResource)))
  1301.         return "ImmutableFolder";
  1302.  
  1303.       // not a container; make sure it has at least a URL
  1304.       if (this.getProperty(aResource, NC_NS+"URL") != null)
  1305.         return "ImmutableBookmark";
  1306.     }
  1307.  
  1308.     return type;
  1309.   },
  1310.  
  1311.   
  1312.   /////////////////////////////////////////////////////////////////////////////
  1313.   // Caches frequently used informations about the selection
  1314.   checkSelection: function (aSelection)
  1315.   {
  1316.     if (aSelection.length == 0)
  1317.       return;
  1318.  
  1319.     aSelection.type        = new Array(aSelection.length);
  1320.     aSelection.isContainer = new Array(aSelection.length);
  1321.     aSelection.containsPTF = false;
  1322.     aSelection.containsImmutable = false;
  1323.     var index, item, parent, type, ptype, protocol, isContainer, isImmutable;
  1324.     for (var i=0; i<aSelection.length; ++i) {
  1325.       item        = aSelection.item[i];
  1326.       parent      = aSelection.parent[i];
  1327.       type        = BookmarksUtils.resolveType(item);
  1328.       protocol    = item.Value.split(":")[0];
  1329.       isContainer = RDFCU.IsContainer(BMDS, item) ||
  1330.                     protocol == "find" || protocol == "file";
  1331.       isImmutable = false;
  1332.       if (item.Value == "NC:BookmarksRoot") {
  1333.         isImmutable = true;
  1334.       }
  1335.       else if (type != "Bookmark" && type != "BookmarkSeparator" && 
  1336.                type != "Folder"   && type != "PersonalToolbarFolder" &&
  1337.                type != "Livemark")
  1338.         isImmutable = true;
  1339.       else if (parent) {
  1340.         var ptype = BookmarksUtils.resolveType(parent);
  1341.         if (ptype == "Livemark")
  1342.           isImmutable = true;
  1343.         var parentProtocol = parent.Value.split(":")[0];
  1344.         if (parentProtocol == "find" || parentProtocol == "file")
  1345.           aSelection.parent[i] = null;
  1346.       }
  1347.       if (isImmutable)
  1348.         aSelection.containsImmutable = true;
  1349.  
  1350.       aSelection.type       [i] = type;
  1351.       aSelection.isContainer[i] = isContainer;
  1352.     }
  1353.     if (this.isContainerChildOrSelf(BMSVC.getBookmarksToolbarFolder(), aSelection))
  1354.       aSelection.containsPTF = true;
  1355.   },
  1356.  
  1357.   isSelectionValidForInsertion: function (aSelection, aTarget)
  1358.   {
  1359.     return BookmarksUtils.isValidTargetContainer(aTarget.parent, aSelection)
  1360.   },
  1361.  
  1362.   isSelectionValidForDeletion: function (aSelection)
  1363.   {
  1364.     return !aSelection.containsImmutable && !aSelection.containsPTF;
  1365.   },
  1366.  
  1367.   /////////////////////////////////////////////////////////////////////////////
  1368.   // Returns true is aContainer is a member or a child of the selection
  1369.   isContainerChildOrSelf: function (aContainer, aSelection)
  1370.   {
  1371.     var folder = aContainer;
  1372.     do {
  1373.       for (var i=0; i<aSelection.length; ++i) {
  1374.         if (aSelection.isContainer[i] && aSelection.item[i].Value == folder.Value)
  1375.           return true;
  1376.       }
  1377.       folder = BMSVC.getParent(folder);
  1378.       if (!folder)
  1379.         return false; // sanity check
  1380.     } while (folder.Value != "NC:BookmarksRoot")
  1381.     return false;
  1382.   },
  1383.  
  1384.   /////////////////////////////////////////////////////////////////////////////
  1385.   // Returns true if aSelection can be inserted in aFolder
  1386.   isValidTargetContainer: function (aFolder, aSelection)
  1387.   {
  1388.     if (!aFolder)
  1389.       return false;
  1390.     if (aFolder.Value == "NC:BookmarksTopRoot")
  1391.       return false;
  1392.     if (aFolder.Value == "NC:BookmarksRoot")
  1393.       return true;
  1394.  
  1395.     // don't insert items in an invalid container
  1396.     // 'file:' and 'find:' items have a 'Bookmark' type
  1397.     var type = BookmarksUtils.resolveType(aFolder);
  1398.     if (type != "Folder" && type != "PersonalToolbarFolder")
  1399.       return false;
  1400.  
  1401.     // bail if we just check the container
  1402.     if (!aSelection)
  1403.       return true;
  1404.  
  1405.     // check that the selected folder is not the selected item nor its child
  1406.     if (this.isContainerChildOrSelf(aFolder, aSelection))
  1407.       return false;
  1408.  
  1409.     return true;
  1410.   },
  1411.  
  1412.   /////////////////////////////////////////////////////////////////////////////
  1413.   removeAndCheckSelection: function (aAction, aSelection)
  1414.   {
  1415.     isValid = BookmarksUtils.isSelectionValidForDeletion(aSelection);
  1416.     if (!isValid) {
  1417.       SOUND.beep();
  1418.       return false;
  1419.     }
  1420.     this.removeSelection(aAction, aSelection);
  1421.     BookmarksUtils.flushDataSource();
  1422.     return true;
  1423.   },
  1424.  
  1425.   removeSelection: function (aAction, aSelection)
  1426.   {
  1427.     var transaction    = new BookmarkRemoveTransaction(aAction);
  1428.     transaction.item   = [];
  1429.     transaction.parent = [];
  1430.     transaction.index  = [];
  1431.     for (var i = 0; i < aSelection.length; ++i) {
  1432.       if (aSelection.parent[i]) {
  1433.         RDFC.Init(BMDS, aSelection.parent[i]);
  1434.         transaction.item  .push(aSelection.item[i]);
  1435.         transaction.parent.push(aSelection.parent[i]);
  1436.         transaction.index .push(RDFC.IndexOf(aSelection.item[i]));
  1437.       }
  1438.     }
  1439.     BMSVC.transactionManager.doTransaction(transaction);
  1440.     return true;
  1441.   },
  1442.         
  1443.   insertAndCheckSelection: function (aAction, aSelection, aTarget, aTargetIndex)
  1444.   {
  1445.     isValid = BookmarksUtils.isSelectionValidForInsertion(aSelection, aTarget);
  1446.     if (!isValid) {
  1447.       SOUND.beep();
  1448.       return false;
  1449.     }
  1450.     this.insertSelection(aAction, aSelection, aTarget, aTargetIndex);
  1451.     BookmarksUtils.flushDataSource();
  1452.     return true;
  1453.   },
  1454.  
  1455.   insertSelection: function (aAction, aSelection, aTarget, aTargetIndex)
  1456.   {
  1457.     var transaction    = new BookmarkInsertTransaction(aAction);
  1458.     transaction.item   = new Array(aSelection.length);
  1459.     transaction.parent = new Array(aSelection.length);
  1460.     transaction.index  = new Array(aSelection.length);
  1461.     var index = aTarget.index;
  1462.     for (var i=0; i<aSelection.length; ++i) {
  1463.       var rSource = aSelection.item[i];
  1464.       if (BMSVC.isBookmarkedResource(rSource))
  1465.         rSource = BMSVC.cloneResource(rSource);
  1466.       transaction.item  [i] = rSource;
  1467.       transaction.parent[i] = aTarget.parent;
  1468.       // Broken Insert Code attempts to always insert items in the
  1469.       // right place (i.e. after the selected item).  However, because
  1470.       // of RDF Container suckyness, this code gets very confused, due
  1471.       // to rdf container indexes not matching up to number of items,
  1472.       // and because we can't trust GetCount to return a real count.
  1473.       // The -1 is there to handle inserting into the persontal toolbar
  1474.       // folder via right-click on the PTF.
  1475.       if (aTarget.index == -1) {
  1476.         transaction.index[i] = -1;
  1477.       } else {
  1478.       transaction.index [i] = index++;
  1479.       }
  1480.     }
  1481.     BMSVC.transactionManager.doTransaction(transaction);
  1482.   },
  1483.  
  1484.   moveAndCheckSelection: function (aAction, aSelection, aTarget)
  1485.   {
  1486.     var isValid = BookmarksUtils.isSelectionValidForDeletion(aSelection) &&
  1487.                   BookmarksUtils.isSelectionValidForInsertion(aSelection, aTarget);
  1488.     if (!isValid) {
  1489.       SOUND.beep();
  1490.       return false;
  1491.     }
  1492.     this.moveSelection(aAction, aSelection, aTarget);
  1493.     BookmarksUtils.flushDataSource();
  1494.     return true;
  1495.   },
  1496.  
  1497.   moveSelection: function (aAction, aSelection, aTarget)
  1498.   {
  1499.     var txn = new BookmarkMoveTransaction(aAction, aSelection, aTarget);
  1500.     BMSVC.transactionManager.doTransaction(txn);
  1501.   }, 
  1502.  
  1503.   // returns true if this selection should be copied instead of moved,
  1504.   // if a move was originally requested
  1505.   shouldCopySelection: function (aAction, aSelection)
  1506.   {
  1507.     for (var i = 0; i < aSelection.length; i++) {
  1508.       var parentType = BookmarksUtils.resolveType(aSelection.parent[i]);
  1509.       if (aSelection.type[i] == "ImmutableBookmark" ||
  1510.           aSelection.type[i] == "ImmutableFolder" ||
  1511.           aSelection.parent[i] == null ||
  1512.           (aSelection.type[i] == "Bookmark" && parentType == "Livemark"))
  1513.       {
  1514.         return true;            // if any of these are found
  1515.       }
  1516.     }
  1517.  
  1518.     return false;
  1519.   },
  1520.  
  1521.   getXferDataFromSelection: function (aSelection)
  1522.   {
  1523.     if (aSelection.length == 0)
  1524.       return null;
  1525.     var dataSet = new TransferDataSet();
  1526.     var data, item, itemUrl, itemName, parent, name;
  1527.     for (var i=0; i<aSelection.length; ++i) {
  1528.       data     = new TransferData();
  1529.       item     = aSelection.item[i].Value;
  1530.       itemUrl  = this.getProperty(item, NC_NS+"URL");
  1531.       itemName = this.getProperty(item, NC_NS+"Name");
  1532.       parent   = aSelection.parent[i].Value;
  1533.       data.addDataForFlavour("moz/rdfitem",    item+"\n"+(parent?parent:""));
  1534.       data.addDataForFlavour("text/x-moz-url", itemUrl+"\n"+itemName);
  1535.       data.addDataForFlavour("text/html",      "<A HREF='"+itemUrl+"'>"+itemName+"</A>");
  1536.       data.addDataForFlavour("text/unicode",   itemUrl);
  1537.       dataSet.push(data);
  1538.     }
  1539.     return dataSet;
  1540.   },
  1541.  
  1542.   getSelectionFromXferData: function (aDragSession)
  1543.   {
  1544.     var selection    = {};
  1545.     selection.item   = [];
  1546.     selection.parent = [];
  1547.     var trans = Components.classes["@mozilla.org/widget/transferable;1"]
  1548.                           .createInstance(Components.interfaces.nsITransferable);
  1549.     trans.addDataFlavor("moz/rdfitem");
  1550.     trans.addDataFlavor("text/x-moz-url");
  1551.     trans.addDataFlavor("text/unicode");
  1552.     var uri, extra, rSource, rParent, parent;
  1553.     for (var i = 0; i < aDragSession.numDropItems; ++i) {
  1554.       var bestFlavour = {}, dataObj = {}, len = {};
  1555.       aDragSession.getData(trans, i);
  1556.       trans.getAnyTransferData(bestFlavour, dataObj, len);
  1557.       dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
  1558.       if (!dataObj)
  1559.         continue;
  1560.       dataObj = dataObj.data.substring(0, len.value).split("\n");
  1561.       uri     = dataObj[0];
  1562.       if (dataObj.length > 1 && dataObj[1] != "")
  1563.         extra = dataObj[1];
  1564.       else
  1565.         extra = null;
  1566.       switch (bestFlavour.value) {
  1567.       case "moz/rdfitem":
  1568.         rSource = RDF.GetResource(uri);
  1569.         parent  = extra;
  1570.         break;
  1571.       case "text/x-moz-url":
  1572.       case "text/unicode":
  1573.         rSource = BookmarksUtils.createBookmark(null, uri, null, extra, null);
  1574.         parent = null;
  1575.         break;
  1576.       }
  1577.       selection.item.push(rSource);
  1578.       if (parent)
  1579.         rParent = RDF.GetResource(parent);
  1580.       else
  1581.         rParent = null;
  1582.       selection.parent.push(rParent);
  1583.     }
  1584.     selection.length = selection.item.length;
  1585.     BookmarksUtils.checkSelection(selection);
  1586.     return selection;
  1587.   },
  1588.  
  1589.   getTargetFromFolder: function(aResource)
  1590.   {
  1591.     var index = parseInt(this.getProperty(aResource, RDF_NS+"nextVal"));
  1592.     if (isNaN(index))
  1593.       return {parent: null, index: -1};
  1594.     else
  1595.       return {parent: aResource, index: index};
  1596.   },
  1597.  
  1598.   getSelectionFromResource: function (aItem, aParent)
  1599.   {
  1600.     var selection    = {};
  1601.     selection.length = 1;
  1602.     selection.item   = [aItem  ];
  1603.     selection.parent = [aParent];
  1604.     this.checkSelection(selection);
  1605.     return selection;
  1606.   },
  1607.  
  1608.   createBookmark: function (aName, aURL, aCharSet, aDefaultName)
  1609.   {
  1610.     if (!aName) {
  1611.       // look up in the history ds to retrieve the name
  1612.       var rSource = RDF.GetResource(aURL);
  1613.       var HISTDS  = RDF.GetDataSource("rdf:history");
  1614.       var nameArc = RDF.GetResource(NC_NS+"Name");
  1615.       var rName   = HISTDS.GetTarget(rSource, nameArc, true);
  1616.       aName       = rName ? rName.QueryInterface(kRDFLITIID).Value : aDefaultName;
  1617.       if (!aName)
  1618.         aName = aURL;
  1619.     }
  1620.     if (!aCharSet) {
  1621.       var fw = document.commandDispatcher.focusedWindow;
  1622.       if (fw)
  1623.         aCharSet = fw.document.characterSet;
  1624.     }
  1625.     return BMSVC.createBookmark(aName, aURL, null, null, aCharSet, null);
  1626.   },
  1627.  
  1628.   createLivemark: function (aName, aURL, aFeedURL, aDefaultName)
  1629.   {
  1630.     if (!aName) {
  1631.       // look up in the history ds to retrieve the name
  1632.       var rSource = RDF.GetResource(aURL);
  1633.       var HISTDS  = RDF.GetDataSource("rdf:history");
  1634.       var nameArc = RDF.GetResource(NC_NS+"Name");
  1635.       var rName   = HISTDS.GetTarget(rSource, nameArc, true);
  1636.       aName       = rName ? rName.QueryInterface(kRDFLITIID).Value : aDefaultName;
  1637.       if (!aName)
  1638.         aName = aURL;
  1639.     }
  1640.     return BMSVC.createLivemark(aName, aURL, aFeedURL, null);
  1641.   },
  1642.  
  1643.   flushDataSource: function ()
  1644.   {
  1645.     var remoteDS = BMDS.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
  1646.     setTimeout(function () {remoteDS.Flush()}, 100);
  1647.   },
  1648.  
  1649.   // should update the caller, aShowDialog is no more necessary
  1650.   addBookmark: function (aURL, aTitle, aCharset, aIsWebPanel)
  1651.   {
  1652.     openDialog("chrome://browser/content/bookmarks/addBookmark2.xul", "",
  1653.                "centerscreen,chrome,dialog,resizable,dependent", aTitle, aURL, null, aCharset,
  1654.                null, null, aIsWebPanel, null, null, null, null, false);
  1655.   },
  1656.  
  1657.   addBookmarkFromMultibar: function (aURL, aTitle, aCharset, aIsWebPanel)
  1658.   {
  1659.     openDialog("chrome://browser/content/bookmarks/addBookmark2.xul", "",
  1660.                "centerscreen,chrome,dialog,resizable,dependent", aTitle, aURL, null, aCharset,
  1661.                null, null, aIsWebPanel, null, null, null, null, true);
  1662.   }, 
  1663.  
  1664.   addLivemark: function (aURL, aFeedURL, aTitle)
  1665.   {
  1666.     openDialog("chrome://browser/content/bookmarks/addBookmark2.xul", "",
  1667.                "centerscreen,chrome,dialog,resizable,dependent", aTitle, aURL, null, null,
  1668.                null, null, false, null, null, null, aFeedURL);
  1669.   },
  1670.  
  1671.   loadFavIcon: function (aURL, aFavIconURL) {
  1672.     var urlLiteral = RDF.GetLiteral(aURL);
  1673.     // don't do anything if this URI isn't bookmarked
  1674.     var bmResources = BMSVC.GetSources(RDF.GetResource(NC_NS+"URL"), urlLiteral, true);
  1675.     var toUpdate = 0;
  1676.  
  1677.     while (bmResources.hasMoreElements()) {
  1678.       var bmResource = bmResources.getNext();
  1679.  
  1680.       // don't flag this as needing update if it already has a data: icon url set
  1681.       var oldIcon = BMDS.GetTarget(bmResource, RDF.GetResource(NC_NS+"Icon"), true);
  1682.       if (oldIcon && (oldIcon.QueryInterface(kRDFLITIID).Value.substring(0,5) == "data:"))
  1683.         continue;
  1684.  
  1685.       toUpdate++;
  1686.     }
  1687.  
  1688.     if (toUpdate == 0)
  1689.       return;
  1690.  
  1691.     var chan = IOSVC.newChannel(aFavIconURL, null, null);
  1692.     var listener = new bookmarksFavIconLoadListener (aURL, aFavIconURL, chan);
  1693.     chan.notificationCallbacks = listener;
  1694.     chan.asyncOpen(listener, null);
  1695.   }
  1696. }
  1697.  
  1698. function BookmarkTransaction()
  1699. {
  1700. }
  1701.  
  1702. BookmarkTransaction.prototype = {
  1703.   BATCH_LIMIT : 4,
  1704.   RDFC        : null,
  1705.   BMDS        : null,
  1706.  
  1707.   QueryInterface: function (iid)
  1708.   {
  1709.     if (!iid.equals(Components.interfaces.nsITransaction) &&
  1710.         !iid.equals(Components.interfaces.nsISupports))
  1711.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1712.  
  1713.     return this;
  1714.   },
  1715.  
  1716.   beginUpdateBatch: function()
  1717.   {
  1718.     if (this.item.length > this.BATCH_LIMIT) {
  1719.       this.BMDS.beginUpdateBatch();
  1720.     }
  1721.   },
  1722.  
  1723.   endUpdateBatch: function()
  1724.   {
  1725.     if (this.item.length > this.BATCH_LIMIT) {
  1726.       this.BMDS.endUpdateBatch();
  1727.     }
  1728.   },
  1729.   merge               : function (aTxn)   {return false},
  1730.   getHelperForLanguage: function (aCount) {return null},
  1731.   getInterfaces       : function (aCount) {return null},
  1732.   canCreateWrapper    : function (aIID)   {return "AllAccess"}
  1733. }
  1734.  
  1735. function BookmarkInsertTransaction (aAction)
  1736. {
  1737.   this.wrappedJSObject = this;
  1738.   this.type    = "insert";
  1739.   this.action  = aAction;
  1740.   this.item    = null;
  1741.   this.parent  = null;
  1742.   this.index   = null;
  1743. }
  1744.  
  1745. BookmarkInsertTransaction.prototype =
  1746. {
  1747.   __proto__: BookmarkTransaction.prototype,
  1748.  
  1749.   isTransient: false,
  1750.  
  1751.   doTransaction: function ()
  1752.   {
  1753.     this.beginUpdateBatch();
  1754.     for (var i=0; i<this.item.length; ++i) {
  1755.       this.RDFC.Init(this.BMDS, this.parent[i]);
  1756.       // if the index is -1, we use appendElement, and then update the
  1757.       // index so that undoTransaction can still function
  1758.       if (this.index[i] == -1) {
  1759.         this.RDFC.AppendElement(this.item[i]);
  1760.         this.index[i] = this.RDFC.GetCount();
  1761.       } else {
  1762.         this.RDFC.InsertElementAt(this.item[i], this.index[i], true);
  1763.       }
  1764.     }
  1765.     this.endUpdateBatch();
  1766.   },
  1767.  
  1768.   undoTransaction: function ()
  1769.   {
  1770.     this.beginUpdateBatch();
  1771.     // XXXvarga Can't use |RDFC| here because it's being "reused" elsewhere.
  1772.     var container = Components.classes[kRDFCContractID].createInstance(kRDFCIID);
  1773.     for (var i=this.item.length-1; i>=0; i--) {
  1774.       container.Init(this.BMDS, this.parent[i]);
  1775.       container.RemoveElementAt(this.index[i], true);
  1776.     }
  1777.     this.endUpdateBatch();
  1778.   },
  1779.    
  1780.   redoTransaction: function ()
  1781.   {
  1782.     this.doTransaction();
  1783.   }
  1784. }
  1785.  
  1786. function BookmarkRemoveTransaction (aAction)
  1787. {
  1788.   this.wrappedJSObject = this;
  1789.   this.type    = "remove";
  1790.   this.action  = aAction;
  1791.   this.item    = null;
  1792.   this.parent  = null;
  1793.   this.index   = null;
  1794. }
  1795.  
  1796. BookmarkRemoveTransaction.prototype =
  1797. {
  1798.   __proto__: BookmarkTransaction.prototype,
  1799.  
  1800.   isTransient: false,
  1801.  
  1802.   doTransaction: function ()
  1803.   {
  1804.     this.beginUpdateBatch();
  1805.     for (var i=0; i<this.item.length; ++i) {
  1806.       this.RDFC.Init(this.BMDS, this.parent[i]);
  1807.       this.RDFC.RemoveElementAt(this.index[i], false);
  1808.     }
  1809.     this.endUpdateBatch();
  1810.   },
  1811.  
  1812.   undoTransaction: function ()
  1813.   {
  1814.     this.beginUpdateBatch();
  1815.     for (var i=this.item.length-1; i>=0; i--) {
  1816.       this.RDFC.Init(this.BMDS, this.parent[i]);
  1817.       this.RDFC.InsertElementAt(this.item[i], this.index[i], false);
  1818.     }
  1819.     this.endUpdateBatch();
  1820.   },
  1821.    
  1822.   redoTransaction: function ()
  1823.   {
  1824.     this.doTransaction();
  1825.   }
  1826. }
  1827.  
  1828. function BookmarkMoveTransaction (aAction, aSelection, aTarget)
  1829. {
  1830.   this.wrappedJSObject = this;
  1831.   this.type      = "move";
  1832.   this.action    = aAction;
  1833.   this.selection = aSelection;
  1834.   this.target    = aTarget;
  1835. }
  1836.  
  1837. BookmarkMoveTransaction.prototype =
  1838. {
  1839.   __proto__: BookmarkTransaction.prototype,
  1840.  
  1841.   isTransient: false,
  1842.  
  1843.   beginUpdateBatch: function()
  1844.   {
  1845.     if (this.selection.length > this.BATCH_LIMIT) {
  1846.       this.BMDS.beginUpdateBatch();
  1847.     }
  1848.   },
  1849.  
  1850.   endUpdateBatch: function()
  1851.   {
  1852.     if (this.selection.length > this.BATCH_LIMIT) {
  1853.       this.BMDS.endUpdateBatch();
  1854.     }
  1855.   },
  1856.  
  1857.   doTransaction: function ()
  1858.   {
  1859.     this.beginUpdateBatch();
  1860.     BookmarksUtils.removeSelection("move", this.selection);
  1861.     BookmarksUtils.insertSelection("move", this.selection, this.target);
  1862.     this.endUpdateBatch();
  1863.   },
  1864.  
  1865.   undoTransaction: function () {},
  1866.   redoTransaction: function () {}
  1867. }
  1868.  
  1869. function BookmarkImportTransaction (aAction)
  1870. {
  1871.   this.wrappedJSObject = this;
  1872.   this.type    = "import";
  1873.   this.action  = aAction;
  1874.   this.item    = [];
  1875.   this.parent  = [];
  1876.   this.index   = [];
  1877. }
  1878.  
  1879. BookmarkImportTransaction.prototype =
  1880. {
  1881.   __proto__: BookmarkTransaction.prototype,
  1882.  
  1883.   isTransient: false,
  1884.  
  1885.   doTransaction: function ()
  1886.   {
  1887.   },
  1888.  
  1889.   undoTransaction: function ()
  1890.   {
  1891.     this.beginUpdateBatch();
  1892.     for (var i=this.item.length-1; i>=0; i--) {
  1893.       this.RDFC.Init(this.BMDS, this.parent[i]);
  1894.       this.RDFC.RemoveElementAt(this.index[i], true);
  1895.     }
  1896.     this.endUpdateBatch();
  1897.   },
  1898.    
  1899.   redoTransaction: function ()
  1900.   {
  1901.     this.beginUpdateBatch();
  1902.     for (var i=0; i<this.item.length; ++i) {
  1903.       this.RDFC.Init(this.BMDS, this.parent[i]);
  1904.       this.RDFC.InsertElementAt(this.item[i], this.index[i], true);
  1905.     }
  1906.     this.endUpdateBatch();
  1907.   }
  1908. }
  1909.  
  1910. var BookmarkEditMenuTxnListener =
  1911. {
  1912.  
  1913.   didDo: function (aTxmgr, aTxn)
  1914.   {
  1915.     this.updateMenuItem(aTxmgr, aTxn);
  1916.   },
  1917.  
  1918.   didUndo: function (aTxmgr, aTxn)
  1919.   {
  1920.     this.updateMenuItem(aTxmgr, aTxn);
  1921.   },
  1922.  
  1923.   didRedo: function (aTxmgr, aTxn)
  1924.   {
  1925.     this.updateMenuItem(aTxmgr, aTxn);
  1926.   },
  1927.  
  1928.   didMerge       : function (aTxmgr, aTxn) {},
  1929.   didBeginBatch  : function (aTxmgr, aTxn) {},
  1930.   didEndBatch    : function (aTxmgr, aTxn) {},
  1931.   willDo         : function (aTxmgr, aTxn) {},
  1932.   willUndo       : function (aTxmgr, aTxn) {},
  1933.   willRedo       : function (aTxmgr, aTxn) {},
  1934.   willMerge      : function (aTxmgr, aTxn) {},
  1935.   willBeginBatch : function (aTxmgr, aTxn) {},
  1936.   willEndBatch   : function (aTxmgr, aTxn) {},
  1937.  
  1938.   updateMenuItem: function (aTxmgr, aTxn) {
  1939.     if (aTxn) {
  1940.       aTxn = aTxn.wrappedJSObject;
  1941.       if ((aTxn.type == "remove" || aTxn.type == "insert") && aTxn.action == "move")
  1942.       return;
  1943.     }
  1944.     var node, transactionNumber, transactionList, transactionLabel, action;
  1945.     node = document.getElementById("cmd_undo");
  1946.     transactionNumber = aTxmgr.numberOfUndoItems;
  1947.     dump("N UNDO: "+transactionNumber+"\n")
  1948.     if (transactionNumber == 0) {
  1949.       transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_undo");
  1950.     } else {
  1951.       transactionList  = aTxmgr.getUndoList();
  1952.       action           = transactionList.getItem(transactionNumber-1).wrappedJSObject.action;
  1953.       transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_"+action+"_undo")
  1954.     }
  1955.     node.setAttribute("label", transactionLabel);
  1956.       
  1957.     node = document.getElementById("cmd_redo");
  1958.     transactionNumber = aTxmgr.numberOfRedoItems;
  1959.     dump("N REDO: "+transactionNumber+"\n")
  1960.     if (transactionNumber == 0) {
  1961.       transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_redo");
  1962.     } else {
  1963.       transactionList  = aTxmgr.getRedoList();
  1964.       action           = transactionList.getItem(transactionNumber-1).wrappedJSObject.action;
  1965.       transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_"+action+"_redo")
  1966.     }
  1967.     node.setAttribute("label", transactionLabel);
  1968.   }
  1969. }
  1970.  
  1971. // favicon loaders
  1972.  
  1973. function bookmarksFavIconLoadListener(uri, faviconurl, channel) {
  1974.   this.mURI = uri;
  1975.   this.mFavIconURL = faviconurl;
  1976.   this.mCountRead = 0;
  1977.   this.mChannel = channel;
  1978. }
  1979.  
  1980. bookmarksFavIconLoadListener.prototype = {
  1981.   mURI : null,
  1982.   mFavIconURL : null,
  1983.   mCountRead : null,
  1984.   mChannel : null,
  1985.   mBytes : Array(),
  1986.   mStream : null,
  1987.  
  1988.   QueryInterface: function (iid) {
  1989.     if (!iid.equals(Components.interfaces.nsISupports) &&
  1990.         !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
  1991.         !iid.equals(Components.interfaces.nsIRequestObserver) &&
  1992.         !iid.equals(Components.interfaces.nsIHttpEventSink) &&
  1993.         !iid.equals(Components.interfaces.nsIProgressEventSink) && // see below
  1994.         !iid.equals(Components.interfaces.nsIStreamListener)) {
  1995.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1996.     }
  1997.     return this;
  1998.   },
  1999.  
  2000.   // nsIInterfaceRequestor
  2001.   getInterface: function (iid) {
  2002.     try {
  2003.       return this.QueryInterface(iid);
  2004.     } catch (e) {
  2005.       throw Components.results.NS_NOINTERFACE;
  2006.     }
  2007.   },
  2008.  
  2009.   // nsIRequestObserver
  2010.   onStartRequest : function (aRequest, aContext) {
  2011.     this.mStream = Components.classes['@mozilla.org/binaryinputstream;1'].createInstance(Components.interfaces.nsIBinaryInputStream);
  2012.   },
  2013.  
  2014.   onStopRequest : function (aRequest, aContext, aStatusCode) {
  2015.     var httpChannel = this.mChannel.QueryInterface(Components.interfaces.nsIHttpChannel);
  2016.     if ((httpChannel && httpChannel.requestSucceeded) &&
  2017.         Components.isSuccessCode(aStatusCode) &&
  2018.         this.mCountRead > 0)
  2019.     {
  2020.       var dataurl;
  2021.       // XXX - arbitrary size beyond which we won't store a favicon.  This is /extremely/
  2022.       // generous, and is probably too high.
  2023.       if (this.mCountRead > 16384) {
  2024.         dataurl = "data:";      // hack meaning "pretend this doesn't exist"
  2025.       } else {
  2026.         // get us a mime type for this
  2027.         var mimeType = null;
  2028.  
  2029.         const nsICategoryManager = Components.interfaces.nsICategoryManager;
  2030.         const nsIContentSniffer = Components.interfaces.nsIContentSniffer;
  2031.  
  2032.         var catMgr = Components.classes["@mozilla.org/categorymanager;1"].getService(nsICategoryManager);
  2033.         var sniffers = catMgr.enumerateCategory("content-sniffing-services");
  2034.         while (mimeType == null && sniffers.hasMoreElements()) {
  2035.           var snifferCID = sniffers.getNext().QueryInterface(Components.interfaces.nsISupportsCString).toString();
  2036.           var sniffer = Components.classes[snifferCID].getService(nsIContentSniffer);
  2037.  
  2038.           try {
  2039.             mimeType = sniffer.getMIMETypeFromContent (this.mBytes, this.mCountRead);
  2040.           } catch (e) {
  2041.             mimeType = null;
  2042.             // ignore
  2043.           }
  2044.         }
  2045.       }
  2046.  
  2047.       if (mimeType == null) {
  2048.         BMSVC.updateBookmarkIcon(this.mURI, null, null, 0);
  2049.       } else {
  2050.         BMSVC.updateBookmarkIcon(this.mURI, mimeType, this.mBytes, this.mCountRead);
  2051.       }
  2052.     }
  2053.  
  2054.     this.mChannel = null;
  2055.   },
  2056.  
  2057.   // nsIStreamObserver
  2058.   onDataAvailable : function (aRequest, aContext, aInputStream, aOffset, aCount) {
  2059.     // we could get a different aInputStream, so we don't save this;
  2060.     // it's unlikely we'll get more than one onDataAvailable for a
  2061.     // favicon anyway
  2062.     this.mStream.setInputStream(aInputStream);
  2063.  
  2064.     var chunk = this.mStream.readByteArray(aCount);
  2065.     this.mBytes = this.mBytes.concat(chunk);
  2066.     this.mCountRead += aCount;
  2067.   },
  2068.  
  2069.   // nsIHttpEventSink
  2070.   onRedirect : function (aHttpChannel, aNewChannel) {
  2071.     this.mChannel = aNewChannel;
  2072.   },
  2073.  
  2074.   // nsIProgressEventSink: the only reason we support
  2075.   // nsIProgressEventSink is to shut up a whole slew of xpconnect
  2076.   // warnings in debug builds.  (see bug #253127)
  2077.   onProgress : function (aRequest, aContext, aProgress, aProgressMax) { },
  2078.   onStatus : function (aRequest, aContext, aStatus, aStatusArg) { }
  2079. }
  2080.  
  2081.