home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2005 October / maximum-cd-2005-10.iso / Software / Apps / FirefoxSetup1.0.6.exe / browser.xpi / bin / chrome / browser.jar / content / browser / bookmarks / bookmarks.js < prev    next >
Encoding:
Text File  |  2004-10-28  |  68.6 KB  |  2,006 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.  
  572.     openUILinkIn(url, aTargetBrowser);
  573.   },
  574.  
  575.   openGroupBookmark: function (aURI, aTargetBrowser)
  576.   {
  577.     if (aTargetBrowser == "current" || aTargetBrowser == "tab") {
  578.       var w        = getTopWin();
  579.       var browser  = w.document.getElementById("content");
  580.       var resource = RDF.GetResource(aURI);
  581.       var urlArc   = RDF.GetResource(NC_NS+"URL");
  582.       RDFC.Init(BMDS, resource);
  583.       var containerChildren = RDFC.GetElements();
  584.       var tabPanels = browser.browsers;
  585.       var tabCount  = tabPanels.length;
  586.       var doReplace = PREF.getBoolPref("browser.tabs.loadFolderAndReplace");
  587.       var loadInBackground = PREF.getBoolPref("browser.tabs.loadBookmarksInBackground");
  588.       var index0;
  589.       if (doReplace)
  590.         index0 = 0;
  591.       else {
  592.         for (index0=tabCount-1; index0>=0; --index0)
  593.           if (browser.browsers[index0].webNavigation.currentURI.spec != "about:blank")
  594.             break;
  595.         ++index0;
  596.       }
  597.  
  598.       var index  = index0;
  599.       while (containerChildren.hasMoreElements()) {
  600.         var res = containerChildren.getNext().QueryInterface(kRDFRSCIID);
  601.         var target = BMDS.GetTarget(res, urlArc, true);
  602.         if (target) {
  603.           var uri = target.QueryInterface(kRDFLITIID).Value;
  604.           if (index < tabCount)
  605.             tabPanels[index].loadURI(uri);
  606.           else
  607.             browser.addTab(uri);
  608.           ++index;
  609.         }
  610.       }
  611.  
  612.       // If the bookmark group was completely invalid, just bail.
  613.       if (index == index0)
  614.         return;
  615.  
  616.       // focus the first tab if prefs say to
  617.       if (!loadInBackground || doReplace) {
  618.         // Select the first tab in the group.
  619.         var tabs = browser.mTabContainer.childNodes;
  620.         browser.selectedTab = tabs[index0];
  621.       }
  622.  
  623.       // Close any remaining open tabs that are left over.
  624.       // (Always skipped when we append tabs)
  625.       for (var i = tabCount-1; i >= index; --i)
  626.         browser.removeTab(tabs[i]);
  627.  
  628.       // and focus the content
  629.       w._content.focus();
  630.  
  631.     } else {
  632.       dump("Open Group in new window: not implemented...\n");
  633.     }
  634.   },
  635.  
  636.   createNewBookmark: function (aTarget)
  637.   {
  638.     var name     = BookmarksUtils.getLocaleString("ile_newbookmark");
  639.     var resource = BMSVC.createBookmark(name, "", "", "", "", null);
  640.     this.createNewResource(resource, aTarget, "newbookmark");
  641.   },
  642.  
  643.   createNewLivemark: function (aTarget)
  644.   {
  645.     var name     = BookmarksUtils.getLocaleString("ile_newlivemark");
  646.     var resource = BMSVC.createLivemark(name, "", "", null);
  647.     this.createNewResource(resource, aTarget, "newlivemark");
  648.   },
  649.  
  650.   createNewFolder: function (aTarget)
  651.   {
  652.     var name     = BookmarksUtils.getLocaleString("ile_newfolder");
  653.     var resource = BMSVC.createFolder(name);
  654.     this.createNewResource(resource, aTarget, "newfolder");
  655.     // temporary hack...
  656.     return resource;
  657.   },
  658.  
  659.   createNewSeparator: function (aTarget)
  660.   {
  661.     var resource = BMSVC.createSeparator();
  662.     this.createNewResource(resource, aTarget, "newseparator");
  663.   },
  664.  
  665.   createNewResource: function(aResource, aTarget, aTxnType)
  666.   {
  667.     var selection = BookmarksUtils.getSelectionFromResource(aResource, aTarget.parent);
  668.     var ok        = BookmarksUtils.insertAndCheckSelection(aTxnType, selection, aTarget, -1);
  669.     if (ok && aTxnType != "newseparator") {
  670.       ok = this.openBookmarkProperties(selection);
  671.       if (!ok)
  672.         BookmarksCommand.deleteBookmark(selection);
  673.     }
  674.   },
  675.  
  676.   importBookmarks: function ()
  677.   {
  678.       // XXX: ifdef it to be non-modal (non-"sheet") on mac (see bug 259039)
  679.       var features = "modal,centerscreen,chrome,resizable=no";
  680.       window.fromFile = false;
  681.       window.openDialog("chrome://browser/content/migration/migration.xul", "migration", features, "bookmarks");
  682.       if(window.fromFile)
  683.       {
  684.         this.importBookmarksFromFile();
  685.       }
  686.   },
  687.  
  688.   importBookmarksFromFile: function ()
  689.   {
  690.     ///transaction...
  691.     try {
  692.       const kFilePickerContractID = "@mozilla.org/filepicker;1";
  693.       const kFilePickerIID = Components.interfaces.nsIFilePicker;
  694.       const kFilePicker = Components.classes[kFilePickerContractID].createInstance(kFilePickerIID);
  695.     
  696.       const kTitle = BookmarksUtils.getLocaleString("SelectImport");
  697.       kFilePicker.init(window, kTitle, kFilePickerIID["modeOpen"]);
  698.       kFilePicker.appendFilters(kFilePickerIID.filterHTML | kFilePickerIID.filterAll);
  699.       var fileName;
  700.       if (kFilePicker.show() != kFilePickerIID.returnCancel) {
  701.         fileName = kFilePicker.file.path;
  702.         if (!fileName) return;
  703.       }
  704.       else return;
  705.     }
  706.     catch (e) {
  707.       return;
  708.     }
  709.     rTarget = RDF.GetResource("NC:BookmarksRoot");
  710.     RDFC.Init(BMDS, rTarget);
  711.     var countBefore = parseInt(BookmarksUtils.getProperty(rTarget, RDF_NS+"nextVal"));
  712.     var args = [{ property: NC_NS+"URL", literal: fileName}];
  713.     this.doBookmarksCommand(rTarget, NC_NS_CMD+"import", args);
  714.     var countAfter = parseInt(BookmarksUtils.getProperty(rTarget, RDF_NS+"nextVal"));
  715.  
  716.     var transaction = new BookmarkImportTransaction("import");
  717.     for (var index = countBefore; index < countAfter; index++) {
  718.       var nChildArc = RDFCU.IndexToOrdinalResource(index);
  719.       var rChild    = BMDS.GetTarget(rTarget, nChildArc, true);
  720.       transaction.item   .push(rChild);
  721.       transaction.parent .push(rTarget);
  722.       transaction.index  .push(index);
  723.     }
  724.     BMSVC.transactionManager.doTransaction(transaction);
  725.     BookmarksUtils.flushDataSource();
  726.   },
  727.  
  728.   exportBookmarks: function ()
  729.   {
  730.     try {
  731.       const kFilePickerContractID = "@mozilla.org/filepicker;1";
  732.       const kFilePickerIID = Components.interfaces.nsIFilePicker;
  733.       const kFilePicker = Components.classes[kFilePickerContractID].createInstance(kFilePickerIID);
  734.       
  735.       const kTitle = BookmarksUtils.getLocaleString("EnterExport");
  736.       kFilePicker.init(window, kTitle, kFilePickerIID["modeSave"]);
  737.       kFilePicker.appendFilters(kFilePickerIID.filterHTML | kFilePickerIID.filterAll);
  738.       kFilePicker.defaultString = "bookmarks.html";
  739.       var fileName;
  740.       if (kFilePicker.show() != kFilePickerIID.returnCancel) {
  741.         fileName = kFilePicker.file.path;
  742.         if (!fileName) return;
  743.       }
  744.       else return;
  745.  
  746.       var file = Components.classes["@mozilla.org/file/local;1"]
  747.                            .createInstance(Components.interfaces.nsILocalFile);
  748.       if (!file)
  749.         return;
  750.       file.initWithPath(fileName);
  751.       if (!file.exists()) {
  752.         file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
  753.       }
  754.     }
  755.     catch (e) {
  756.       return;
  757.     }
  758.     var selection = RDF.GetResource("NC:BookmarksRoot");
  759.     var args = [{ property: NC_NS+"URL", literal: fileName}];
  760.     this.doBookmarksCommand(selection, NC_NS_CMD+"export", args);
  761.   },
  762.  
  763.   refreshLivemark: function (aSelection)
  764.   {
  765.     var exp = RDF.GetResource(NC_NS+"LivemarkExpiration");
  766.     for (var i = 0; i < aSelection.length; i++) {
  767.       rsrc = RDF.GetResource(aSelection.item[i].Value);
  768.       oldtgt = BMDS.GetTarget(rsrc, exp, true);
  769.       if (oldtgt) {
  770.         BMDS.Unassert(rsrc, exp, oldtgt);
  771.       }
  772.     }
  773.   },
  774.  
  775.   sortByName: function (aSelection)
  776.   {
  777.     // do the real sorting in a timeout, to make sure that
  778.     // if we sort from a menu that the menu gets torn down
  779.     // before we sort.  the template builder really doesn't
  780.     // like it if we move things around; the menu code also
  781.     // doesn't like it if we move the menuparent while a
  782.     // popup is open.
  783.     setTimeout(function () { BookmarksCommand.realSortByName(aSelection); }, 0);
  784.   },
  785.  
  786.   realSortByName: function (aSelection)
  787.   {
  788.     var theFolder;
  789.  
  790.     if (aSelection.length != 1)
  791.       return;
  792.  
  793.     var selType = BookmarksUtils.resolveType (aSelection.item[0]);
  794.     if (selType == "Folder" || selType == "Bookmark" ||
  795.         selType == "PersonalToolbarFolder" || selType == "Livemark")
  796.     {
  797.       theFolder = aSelection.parent[0];
  798.     } else {
  799.       // we're not going to try to sort ImmutableBookmark siblings or
  800.       // any other such thing, since it'll probably just get us into
  801.       // trouble
  802.       return;
  803.     }
  804.  
  805.     var toSort = [];
  806.     RDFC.Init(BMDS, theFolder);
  807.     var folderContents = RDFC.GetElements();
  808.     while (folderContents.hasMoreElements()) {
  809.         var rsrc = folderContents.getNext().QueryInterface(kRDFRSCIID);
  810.         var rtype = BookmarksUtils.resolveType(rsrc);
  811.         if (rtype == "BookmarkSeparator")
  812.           continue;
  813.         toSort.push(rsrc);
  814.     }
  815.  
  816.     const kName = RDF.GetResource(NC_NS+"Name");
  817.  
  818.     var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
  819.                                   .getService(Components.interfaces.nsILocaleService);
  820.     var collationFactory = Components.classes["@mozilla.org/intl/collation-factory;1"]
  821.                                      .getService(Components.interfaces.nsICollationFactory);
  822.     var collation = collationFactory.CreateCollation(localeService.getApplicationLocale());
  823.  
  824.     toSort.sort (function (a, b) {
  825.                    var atype = BookmarksUtils.resolveType(a);
  826.                    var btype = BookmarksUtils.resolveType(b);
  827.  
  828.                    var aisfolder = (atype == "Folder") || (atype == "PersonalToolbarFolder");
  829.                    var bisfolder = (btype == "Folder") || (btype == "PersonalToolbarFolder");
  830.  
  831.                    // folders above bookmarks
  832.                    if (aisfolder && !bisfolder)
  833.                      return -1;
  834.                    if (bisfolder && !aisfolder)
  835.                      return 1;
  836.  
  837.                    // then sort by name
  838.                    var aname = BMDS.GetTarget(a, kName, true).QueryInterface(kRDFLITIID).Value;
  839.                    var bname = BMDS.GetTarget(b, kName, true).QueryInterface(kRDFLITIID).Value;
  840.  
  841.                    return collation.compareString(0, aname, bname);
  842.                  });
  843.  
  844.     // we now have the resources here sorted by name
  845.     BMDS.beginUpdateBatch();
  846.  
  847.     RDFC.Init(BMDS, theFolder);
  848.  
  849.     // remove existing elements
  850.     var folderContents = RDFC.GetElements();
  851.     while (folderContents.hasMoreElements()) {
  852.       RDFC.RemoveElement (folderContents.getNext(), false);
  853.     }
  854.  
  855.     // and add our elements back
  856.     for (var i = 0; i < toSort.length; i++) {
  857.       RDFC.InsertElementAt (toSort[i], i+1, true);
  858.     }
  859.  
  860.     BMDS.endUpdateBatch();
  861.   }
  862.  
  863. }
  864.  
  865.   /////////////////////////////////////////////////////////////////////////////
  866.   // Command handling & Updating.
  867. var BookmarksController = {
  868.  
  869.   supportsCommand: function (aCommand)
  870.   {
  871.     var isCommandSupported;
  872.     switch(aCommand) {
  873.     case "cmd_undo":
  874.     case "cmd_redo":
  875.     case "cmd_bm_undo":
  876.     case "cmd_bm_redo":
  877.     case "cmd_cut":
  878.     case "cmd_copy":
  879.     case "cmd_paste":
  880.     case "cmd_delete":
  881.     case "cmd_selectAll":
  882.     case "cmd_bm_open":
  883.     case "cmd_bm_openinnewwindow":
  884.     case "cmd_bm_openinnewtab":
  885.     case "cmd_bm_expandfolder":
  886.     case "cmd_bm_openfolder":
  887.     case "cmd_bm_managefolder":
  888.     case "cmd_bm_newbookmark":
  889.     case "cmd_bm_newlivemark":
  890.     case "cmd_bm_newfolder":
  891.     case "cmd_bm_newseparator":
  892.     case "cmd_bm_properties":
  893.     case "cmd_bm_rename":
  894.     case "cmd_bm_setnewbookmarkfolder":
  895.     case "cmd_bm_setpersonaltoolbarfolder":
  896.     case "cmd_bm_setnewsearchfolder":
  897.     case "cmd_bm_import":
  898.     case "cmd_bm_export":
  899.     case "cmd_bm_movebookmark":
  900.     case "cmd_bm_refreshlivemark":
  901.     case "cmd_bm_sortbyname":
  902.       isCommandSupported = true;
  903.       break;
  904.     default:
  905.       isCommandSupported = false;
  906.     }
  907.     //if (!isCommandSupported)
  908.     //  dump("Bookmark command '"+aCommand+"' is not supported!\n");
  909.     return isCommandSupported;
  910.   },
  911.  
  912.   isCommandEnabled: function (aCommand, aSelection, aTarget)
  913.   {
  914.     var item0, type0, junk;
  915.     var length = 0;
  916.     if (aSelection && aSelection.length != 0) {
  917.       length = aSelection.length;
  918.       item0 = aSelection.item[0].Value;
  919.       type0 = aSelection.type[0];
  920.     }
  921.     var i;
  922.  
  923.     switch(aCommand) {
  924.     case "cmd_undo":
  925.     case "cmd_bm_undo":
  926.       return BMSVC.transactionManager.numberOfUndoItems > 0;
  927.     case "cmd_redo":
  928.     case "cmd_bm_redo":
  929.       return BMSVC.transactionManager.numberOfRedoItems > 0;
  930.     case "cmd_paste":
  931.       if (aTarget && !BookmarksUtils.isValidTargetContainer(aTarget.parent))
  932.         return false;
  933.       const kClipboardContractID = "@mozilla.org/widget/clipboard;1";
  934.       const kClipboardIID = Components.interfaces.nsIClipboard;
  935.       var clipboard = Components.classes[kClipboardContractID].getService(kClipboardIID);
  936.       const kSuppArrayContractID = "@mozilla.org/supports-array;1";
  937.       const kSuppArrayIID = Components.interfaces.nsISupportsArray;
  938.       var flavourArray = Components.classes[kSuppArrayContractID].createInstance(kSuppArrayIID);
  939.       const kSuppStringContractID = "@mozilla.org/supports-cstring;1";
  940.       const kSuppStringIID = Components.interfaces.nsISupportsCString;
  941.     
  942.       var flavours = ["moz/bookmarkclipboarditem", "text/x-moz-url"];
  943.       for (i = 0; i < flavours.length; ++i) {
  944.         const kSuppString = Components.classes[kSuppStringContractID].createInstance(kSuppStringIID);
  945.         kSuppString.data = flavours[i];
  946.         flavourArray.AppendElement(kSuppString);
  947.       }
  948.       var hasFlavours = clipboard.hasDataMatchingFlavors(flavourArray, kClipboardIID.kGlobalClipboard);
  949.       return hasFlavours;
  950.     case "cmd_copy":
  951.       return length > 0;
  952.     case "cmd_cut":
  953.     case "cmd_delete":
  954.       return length > 0 && !aSelection.containsImmutable && !aSelection.containsPTF;
  955.     case "cmd_selectAll":
  956.       return true;
  957.     case "cmd_bm_open":
  958.     case "cmd_bm_expandfolder":
  959.     case "cmd_bm_managefolder":
  960.       return length == 1;
  961.     case "cmd_bm_openinnewwindow":
  962.     case "cmd_bm_openinnewtab":
  963.       return true;
  964.     case "cmd_bm_openfolder":
  965.       for (i=0; i<length; ++i) {
  966.         if (aSelection.type[i] == "ImmutableBookmark" ||
  967.             aSelection.type[i] == "ImmutableFolder" ||
  968.             aSelection.type[i] == "Bookmark" ||
  969.             aSelection.type[i] == "BookmarkSeparator")
  970.           return false;
  971.         RDFC.Init(BMDS, aSelection.item[i]);
  972.         var children = RDFC.GetElements();
  973.         while (children.hasMoreElements()) {
  974.           var childType = BookmarksUtils.resolveType(children.getNext());
  975.           if (childType == "Bookmark" || childType == "LivemarkBookmark")
  976.             return true;
  977.         }
  978.       }
  979.       return false;
  980.     case "cmd_bm_import":
  981.     case "cmd_bm_export":
  982.       return true;
  983.     case "cmd_bm_newbookmark":
  984.     case "cmd_bm_newlivemark":
  985.     case "cmd_bm_newfolder":
  986.     case "cmd_bm_newseparator":
  987.       return ((type0 == "PersonalToolbarFolder") ||
  988.               (aTarget && BookmarksUtils.isValidTargetContainer(aTarget.parent)));
  989.     case "cmd_bm_properties":
  990.     case "cmd_bm_rename":
  991.       if (length != 1 ||
  992.           aSelection.item[0].Value == "NC:BookmarksRoot" ||
  993.           BookmarksUtils.resolveType(aSelection.parent[0]) == "Livemark")
  994.         return false;
  995.       return true;
  996.     case "cmd_bm_setpersonaltoolbarfolder":
  997.       if (length != 1 || type0 == "Livemark")
  998.         return false;
  999.       return item0 != BMSVC.getBookmarksToolbarFolder().Value && 
  1000.              item0 != "NC:BookmarksRoot" && type0 == "Folder";
  1001.     case "cmd_bm_movebookmark":
  1002.       return length > 0 && !aSelection.containsImmutable;
  1003.     case "cmd_bm_refreshlivemark":
  1004.       for (i=0; i<length; ++i) {
  1005.         if (aSelection.type[i] != "Livemark")
  1006.           return false;
  1007.       }
  1008.       return length > 0;
  1009.     case "cmd_bm_sortbyname":
  1010.       if (length == 1 && (aSelection.type[0] == "Folder" ||
  1011.                           aSelection.type[0] == "Bookmark" ||
  1012.                           aSelection.type[0] == "PersonalToolbarFolder" ||
  1013.                           aSelection.type[0] == "Livemark"))
  1014.         return true;
  1015.       return false;
  1016.     default:
  1017.       return false;
  1018.     }
  1019.   },
  1020.  
  1021.   doCommand: function (aCommand, aSelection, aTarget, aDS)
  1022.   {
  1023.     var resource0, type0, realTarget;
  1024.     if (aSelection && aSelection.length == 1) {
  1025.       resource0 = aSelection.item[0];
  1026.       type0 = aSelection.type[0];
  1027.     }
  1028.  
  1029.     if (type0 == "PersonalToolbarFolder" && aTarget == null)
  1030.       realTarget = { parent: resource0, index: -1 };
  1031.     else
  1032.       realTarget = aTarget;
  1033.  
  1034.     switch (aCommand) {
  1035.     case "cmd_undo":
  1036.     case "cmd_bm_undo":
  1037.       BookmarksCommand.undoBookmarkTransaction();
  1038.       break;
  1039.     case "cmd_redo":
  1040.     case "cmd_bm_redo":
  1041.       BookmarksCommand.redoBookmarkTransaction();
  1042.       break;
  1043.     case "cmd_bm_open":
  1044.       BookmarksCommand.openBookmark(aSelection, "current", aDS);
  1045.       break;
  1046.     case "cmd_bm_openinnewwindow":
  1047.       BookmarksCommand.openBookmark(aSelection, "window", aDS);
  1048.       break;
  1049.     case "cmd_bm_openinnewtab":
  1050.       BookmarksCommand.openBookmark(aSelection, "tab", aDS);
  1051.       break;
  1052.     case "cmd_bm_openfolder":
  1053.       BookmarksCommand.openBookmark(aSelection, "current", aDS);
  1054.       break;
  1055.     case "cmd_bm_managefolder":
  1056.       BookmarksCommand.manageFolder(aSelection);
  1057.       break;
  1058.     case "cmd_bm_setnewbookmarkfolder":
  1059.     case "cmd_bm_setpersonaltoolbarfolder":
  1060.     case "cmd_bm_setnewsearchfolder":
  1061.       BookmarksCommand.doBookmarksCommand(aSelection.item[0], NC_NS_CMD+aCommand.substring("cmd_bm_".length), []);
  1062.       break;
  1063.     case "cmd_bm_rename":
  1064.     case "cmd_bm_properties":
  1065.       junk = BookmarksCommand.openBookmarkProperties(aSelection);
  1066.       break;
  1067.     case "cmd_cut":
  1068.       BookmarksCommand.cutBookmark(aSelection);
  1069.       break;
  1070.     case "cmd_copy":
  1071.       BookmarksCommand.copyBookmark(aSelection);
  1072.       break;
  1073.     case "cmd_paste":
  1074.       BookmarksCommand.pasteBookmark(realTarget);
  1075.       break;
  1076.     case "cmd_delete":
  1077.       BookmarksCommand.deleteBookmark(aSelection);
  1078.       break;
  1079.     case "cmd_bm_movebookmark":
  1080.       BookmarksCommand.moveBookmark(aSelection);
  1081.       break;
  1082.     case "cmd_bm_newbookmark":
  1083.       BookmarksCommand.createNewBookmark(realTarget);
  1084.       break;
  1085.     case "cmd_bm_newlivemark":
  1086.       BookmarksCommand.createNewLivemark(realTarget);
  1087.       break;
  1088.     case "cmd_bm_newfolder":
  1089.       BookmarksCommand.createNewFolder(realTarget);
  1090.       break;
  1091.     case "cmd_bm_newseparator":
  1092.       BookmarksCommand.createNewSeparator(realTarget);
  1093.       break;
  1094.     case "cmd_bm_import":
  1095.       BookmarksCommand.importBookmarks();
  1096.       break;
  1097.     case "cmd_bm_export":
  1098.       BookmarksCommand.exportBookmarks();
  1099.       break;
  1100.     case "cmd_bm_refreshlivemark":
  1101.       BookmarksCommand.refreshLivemark(aSelection);
  1102.       break;
  1103.     case "cmd_bm_sortbyname":
  1104.       BookmarksCommand.sortByName(aSelection);
  1105.       break;
  1106.     default: 
  1107.       dump("Bookmark command "+aCommand+" not handled!\n");
  1108.     }
  1109.  
  1110.   },
  1111.  
  1112.   onCommandUpdate: function (aSelection, aTarget)
  1113.   {
  1114.     var commands = ["cmd_bm_newbookmark", "cmd_bm_newlivemark", "cmd_bm_newfolder", "cmd_bm_newseparator",
  1115.                     "cmd_undo", "cmd_redo", "cmd_bm_properties", "cmd_bm_rename", 
  1116.                     "cmd_copy", "cmd_paste", "cmd_cut", "cmd_delete",
  1117.                     "cmd_bm_setpersonaltoolbarfolder", "cmd_bm_movebookmark",
  1118.                     "cmd_bm_openfolder", "cmd_bm_managefolder", "cmd_bm_refreshlivemark", "cmd_bm_sortbyname"];
  1119.     for (var i = 0; i < commands.length; ++i) {
  1120.       var enabled = this.isCommandEnabled(commands[i], aSelection, aTarget);
  1121.       var commandNode = document.getElementById(commands[i]);
  1122.      if (commandNode) { 
  1123.         if (enabled) 
  1124.           commandNode.removeAttribute("disabled");
  1125.         else 
  1126.           commandNode.setAttribute("disabled", "true");
  1127.       }
  1128.     }
  1129.   }
  1130. }
  1131.  
  1132. function CommandArrayEnumerator (aCommandArray)
  1133. {
  1134.   this._inner = [];
  1135.   for (var i = 0; i < aCommandArray.length; ++i)
  1136.     this._inner.push(RDF.GetResource(NC_NS_CMD + aCommandArray[i]));
  1137.     
  1138.   this._index = 0;
  1139. }
  1140.  
  1141. CommandArrayEnumerator.prototype = {
  1142.   getNext: function () 
  1143.   {
  1144.     return this._inner[this._index];
  1145.   },
  1146.   
  1147.   hasMoreElements: function ()
  1148.   {
  1149.     return this._index < this._inner.length;
  1150.   }
  1151. };
  1152.  
  1153. var BookmarksUtils = {
  1154.  
  1155.   DROP_BEFORE: Components.interfaces.nsITreeView.inDropBefore,
  1156.   DROP_ON    : Components.interfaces.nsITreeView.inDropOn,
  1157.   DROP_AFTER : Components.interfaces.nsITreeView.inDropAfter,
  1158.  
  1159.   _bundle        : null,
  1160.   _brandShortName: null,
  1161.  
  1162.   /////////////////////////////////////////////////////////////////////////////////////
  1163.   // returns a property from chrome://browser/locale/bookmarks/bookmarks.properties
  1164.   getLocaleString: function (aStringKey, aReplaceString)
  1165.   {
  1166.     if (!this._bundle) {
  1167.       // for those who would xblify Bookmarks.js, there is a need to create string bundle 
  1168.       // manually instead of using <xul:stringbundle/> see bug 63370 for details
  1169.       var LOCALESVC = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
  1170.                                 .getService(Components.interfaces.nsILocaleService);
  1171.       var BUNDLESVC = Components.classes["@mozilla.org/intl/stringbundle;1"]
  1172.                                 .getService(Components.interfaces.nsIStringBundleService);
  1173.       var bookmarksBundle  = "chrome://browser/locale/bookmarks/bookmarks.properties";
  1174.       this._bundle         = BUNDLESVC.createBundle(bookmarksBundle, LOCALESVC.getApplicationLocale());
  1175.       var brandBundle      = "chrome://global/locale/brand.properties";
  1176.       this._brandShortName = BUNDLESVC.createBundle(brandBundle,     LOCALESVC.getApplicationLocale())
  1177.                                       .GetStringFromName("brandShortName");
  1178.     }
  1179.    
  1180.     var bundle;
  1181.     try {
  1182.       if (!aReplaceString)
  1183.         bundle = this._bundle.GetStringFromName(aStringKey);
  1184.       else if (typeof(aReplaceString) == "string")
  1185.         bundle = this._bundle.formatStringFromName(aStringKey, [aReplaceString], 1);
  1186.       else
  1187.         bundle = this._bundle.formatStringFromName(aStringKey, aReplaceString, aReplaceString.length);
  1188.     } catch (e) {
  1189.       dump("Bookmark bundle "+aStringKey+" not found!\n");
  1190.       bundle = "";
  1191.     }
  1192.  
  1193.     bundle = bundle.replace(/%brandShortName%/, this._brandShortName);
  1194.     return bundle;
  1195.   },
  1196.     
  1197.   /////////////////////////////////////////////////////////////////////////////
  1198.   // returns the literal targeted by the URI aArcURI for a resource or uri
  1199.   getProperty: function (aInput, aArcURI, aDS)
  1200.   {
  1201.     var node;
  1202.     var arc  = RDF.GetResource(aArcURI);
  1203.     if (typeof(aInput) == "string") 
  1204.       aInput = RDF.GetResource(aInput);
  1205.     if (!aDS)
  1206.       node = BMDS.GetTarget(aInput, arc, true);
  1207.     else
  1208.       node = aDS .GetTarget(aInput, arc, true);
  1209.     try {
  1210.       return node.QueryInterface(kRDFRSCIID).Value;
  1211.     }
  1212.     catch (e) {
  1213.       return node? node.QueryInterface(kRDFLITIID).Value : "";
  1214.     }    
  1215.   },
  1216.  
  1217.   /////////////////////////////////////////////////////////////////////////////
  1218.   // Determine the rdf:type property for the given resource.
  1219.   resolveType: function (aResource, aDS)
  1220.   {
  1221.     var type = this.getProperty(aResource, RDF_NS+"type", aDS);
  1222.     if (type != "")
  1223.       type = type.split("#")[1];
  1224.     if (type == "Folder") {
  1225.       if (aResource == BMSVC.getBookmarksToolbarFolder())
  1226.         type = "PersonalToolbarFolder";
  1227.     }
  1228.  
  1229.     if (type == "") {
  1230.       // we're not sure what type it is.  figure out if it's a container.
  1231.       var child = this.getProperty(aResource, NC_NS+"child", aDS);
  1232.       if (child || RDFCU.IsContainer(aDS?aDS:BMDS, RDF.GetResource(aResource)))
  1233.         return "ImmutableFolder";
  1234.  
  1235.       // not a container; make sure it has at least a URL
  1236.       if (this.getProperty(aResource, NC_NS+"URL") != null)
  1237.         return "ImmutableBookmark";
  1238.     }
  1239.  
  1240.     return type;
  1241.   },
  1242.  
  1243.   
  1244.   /////////////////////////////////////////////////////////////////////////////
  1245.   // Caches frequently used informations about the selection
  1246.   checkSelection: function (aSelection)
  1247.   {
  1248.     if (aSelection.length == 0)
  1249.       return;
  1250.  
  1251.     aSelection.type        = new Array(aSelection.length);
  1252.     aSelection.isContainer = new Array(aSelection.length);
  1253.     aSelection.containsPTF = false;
  1254.     aSelection.containsImmutable = false;
  1255.     var index, item, parent, type, ptype, protocol, isContainer, isImmutable;
  1256.     for (var i=0; i<aSelection.length; ++i) {
  1257.       item        = aSelection.item[i];
  1258.       parent      = aSelection.parent[i];
  1259.       type        = BookmarksUtils.resolveType(item);
  1260.       protocol    = item.Value.split(":")[0];
  1261.       isContainer = RDFCU.IsContainer(BMDS, item) ||
  1262.                     protocol == "find" || protocol == "file";
  1263.       isImmutable = false;
  1264.       if (item.Value == "NC:BookmarksRoot") {
  1265.         isImmutable = true;
  1266.       }
  1267.       else if (type != "Bookmark" && type != "BookmarkSeparator" && 
  1268.                type != "Folder"   && type != "PersonalToolbarFolder" &&
  1269.                type != "Livemark")
  1270.         isImmutable = true;
  1271.       else if (parent) {
  1272.         var ptype = BookmarksUtils.resolveType(parent);
  1273.         if (ptype == "Livemark")
  1274.           isImmutable = true;
  1275.         var parentProtocol = parent.Value.split(":")[0];
  1276.         if (parentProtocol == "find" || parentProtocol == "file")
  1277.           aSelection.parent[i] = null;
  1278.       }
  1279.       if (isImmutable)
  1280.         aSelection.containsImmutable = true;
  1281.  
  1282.       aSelection.type       [i] = type;
  1283.       aSelection.isContainer[i] = isContainer;
  1284.     }
  1285.     if (this.isContainerChildOrSelf(BMSVC.getBookmarksToolbarFolder(), aSelection))
  1286.       aSelection.containsPTF = true;
  1287.   },
  1288.  
  1289.   isSelectionValidForInsertion: function (aSelection, aTarget)
  1290.   {
  1291.     return BookmarksUtils.isValidTargetContainer(aTarget.parent, aSelection)
  1292.   },
  1293.  
  1294.   isSelectionValidForDeletion: function (aSelection)
  1295.   {
  1296.     return !aSelection.containsImmutable && !aSelection.containsPTF;
  1297.   },
  1298.  
  1299.   /////////////////////////////////////////////////////////////////////////////
  1300.   // Returns true is aContainer is a member or a child of the selection
  1301.   isContainerChildOrSelf: function (aContainer, aSelection)
  1302.   {
  1303.     var folder = aContainer;
  1304.     do {
  1305.       for (var i=0; i<aSelection.length; ++i) {
  1306.         if (aSelection.isContainer[i] && aSelection.item[i].Value == folder.Value)
  1307.           return true;
  1308.       }
  1309.       folder = BMSVC.getParent(folder);
  1310.       if (!folder)
  1311.         return false; // sanity check
  1312.     } while (folder.Value != "NC:BookmarksRoot")
  1313.     return false;
  1314.   },
  1315.  
  1316.   /////////////////////////////////////////////////////////////////////////////
  1317.   // Returns true if aSelection can be inserted in aFolder
  1318.   isValidTargetContainer: function (aFolder, aSelection)
  1319.   {
  1320.     if (!aFolder)
  1321.       return false;
  1322.     if (aFolder.Value == "NC:BookmarksTopRoot")
  1323.       return false;
  1324.     if (aFolder.Value == "NC:BookmarksRoot")
  1325.       return true;
  1326.  
  1327.     // don't insert items in an invalid container
  1328.     // 'file:' and 'find:' items have a 'Bookmark' type
  1329.     var type = BookmarksUtils.resolveType(aFolder);
  1330.     if (type != "Folder" && type != "PersonalToolbarFolder")
  1331.       return false;
  1332.  
  1333.     // bail if we just check the container
  1334.     if (!aSelection)
  1335.       return true;
  1336.  
  1337.     // check that the selected folder is not the selected item nor its child
  1338.     if (this.isContainerChildOrSelf(aFolder, aSelection))
  1339.       return false;
  1340.  
  1341.     return true;
  1342.   },
  1343.  
  1344.   /////////////////////////////////////////////////////////////////////////////
  1345.   removeAndCheckSelection: function (aAction, aSelection)
  1346.   {
  1347.     isValid = BookmarksUtils.isSelectionValidForDeletion(aSelection);
  1348.     if (!isValid) {
  1349.       SOUND.beep();
  1350.       return false;
  1351.     }
  1352.     this.removeSelection(aAction, aSelection);
  1353.     BookmarksUtils.flushDataSource();
  1354.     return true;
  1355.   },
  1356.  
  1357.   removeSelection: function (aAction, aSelection)
  1358.   {
  1359.     var transaction    = new BookmarkRemoveTransaction(aAction);
  1360.     transaction.item   = [];
  1361.     transaction.parent = [];
  1362.     transaction.index  = [];
  1363.     for (var i = 0; i < aSelection.length; ++i) {
  1364.       if (aSelection.parent[i]) {
  1365.         RDFC.Init(BMDS, aSelection.parent[i]);
  1366.         transaction.item  .push(aSelection.item[i]);
  1367.         transaction.parent.push(aSelection.parent[i]);
  1368.         transaction.index .push(RDFC.IndexOf(aSelection.item[i]));
  1369.       }
  1370.     }
  1371.     BMSVC.transactionManager.doTransaction(transaction);
  1372.     return true;
  1373.   },
  1374.         
  1375.   insertAndCheckSelection: function (aAction, aSelection, aTarget, aTargetIndex)
  1376.   {
  1377.     isValid = BookmarksUtils.isSelectionValidForInsertion(aSelection, aTarget);
  1378.     if (!isValid) {
  1379.       SOUND.beep();
  1380.       return false;
  1381.     }
  1382.     this.insertSelection(aAction, aSelection, aTarget, aTargetIndex);
  1383.     BookmarksUtils.flushDataSource();
  1384.     return true;
  1385.   },
  1386.  
  1387.   insertSelection: function (aAction, aSelection, aTarget, aTargetIndex)
  1388.   {
  1389.     var transaction    = new BookmarkInsertTransaction(aAction);
  1390.     transaction.item   = new Array(aSelection.length);
  1391.     transaction.parent = new Array(aSelection.length);
  1392.     transaction.index  = new Array(aSelection.length);
  1393.     var index = aTarget.index;
  1394.     for (var i=0; i<aSelection.length; ++i) {
  1395.       var rSource = aSelection.item[i];
  1396.       if (BMSVC.isBookmarkedResource(rSource))
  1397.         rSource = BMSVC.cloneResource(rSource);
  1398.       transaction.item  [i] = rSource;
  1399.       transaction.parent[i] = aTarget.parent;
  1400.       // Broken Insert Code attempts to always insert items in the
  1401.       // right place (i.e. after the selected item).  However, because
  1402.       // of RDF Container suckyness, this code gets very confused, due
  1403.       // to rdf container indexes not matching up to number of items,
  1404.       // and because we can't trust GetCount to return a real count.
  1405.       // The -1 is there to handle inserting into the persontal toolbar
  1406.       // folder via right-click on the PTF.
  1407.       if (aTarget.index == -1) {
  1408.         transaction.index[i] = -1;
  1409.       } else {
  1410.       transaction.index [i] = index++;
  1411.       }
  1412.     }
  1413.     BMSVC.transactionManager.doTransaction(transaction);
  1414.   },
  1415.  
  1416.   moveAndCheckSelection: function (aAction, aSelection, aTarget)
  1417.   {
  1418.     var isValid = BookmarksUtils.isSelectionValidForDeletion(aSelection) &&
  1419.                   BookmarksUtils.isSelectionValidForInsertion(aSelection, aTarget);
  1420.     if (!isValid) {
  1421.       SOUND.beep();
  1422.       return false;
  1423.     }
  1424.     this.moveSelection(aAction, aSelection, aTarget);
  1425.     BookmarksUtils.flushDataSource();
  1426.     return true;
  1427.   },
  1428.  
  1429.   moveSelection: function (aAction, aSelection, aTarget)
  1430.   {
  1431.     var txn = new BookmarkMoveTransaction(aAction, aSelection, aTarget);
  1432.     BMSVC.transactionManager.doTransaction(txn);
  1433.   }, 
  1434.  
  1435.   // returns true if this selection should be copied instead of moved,
  1436.   // if a move was originally requested
  1437.   shouldCopySelection: function (aAction, aSelection)
  1438.   {
  1439.     for (var i = 0; i < aSelection.length; i++) {
  1440.       var parentType = BookmarksUtils.resolveType(aSelection.parent[i]);
  1441.       if (aSelection.type[i] == "ImmutableBookmark" ||
  1442.           aSelection.type[i] == "ImmutableFolder" ||
  1443.           aSelection.parent[i] == null ||
  1444.           (aSelection.type[i] == "Bookmark" && parentType == "Livemark"))
  1445.       {
  1446.         return true;            // if any of these are found
  1447.       }
  1448.     }
  1449.  
  1450.     return false;
  1451.   },
  1452.  
  1453.   getXferDataFromSelection: function (aSelection)
  1454.   {
  1455.     if (aSelection.length == 0)
  1456.       return null;
  1457.     var dataSet = new TransferDataSet();
  1458.     var data, item, itemUrl, itemName, parent, name;
  1459.     for (var i=0; i<aSelection.length; ++i) {
  1460.       data     = new TransferData();
  1461.       item     = aSelection.item[i].Value;
  1462.       itemUrl  = this.getProperty(item, NC_NS+"URL");
  1463.       itemName = this.getProperty(item, NC_NS+"Name");
  1464.       parent   = aSelection.parent[i].Value;
  1465.       data.addDataForFlavour("moz/rdfitem",    item+"\n"+(parent?parent:""));
  1466.       data.addDataForFlavour("text/x-moz-url", itemUrl+"\n"+itemName);
  1467.       data.addDataForFlavour("text/html",      "<A HREF='"+itemUrl+"'>"+itemName+"</A>");
  1468.       data.addDataForFlavour("text/unicode",   itemUrl);
  1469.       dataSet.push(data);
  1470.     }
  1471.     return dataSet;
  1472.   },
  1473.  
  1474.   getSelectionFromXferData: function (aDragSession)
  1475.   {
  1476.     var selection    = {};
  1477.     selection.item   = [];
  1478.     selection.parent = [];
  1479.     var trans = Components.classes["@mozilla.org/widget/transferable;1"]
  1480.                           .createInstance(Components.interfaces.nsITransferable);
  1481.     trans.addDataFlavor("moz/rdfitem");
  1482.     trans.addDataFlavor("text/x-moz-url");
  1483.     trans.addDataFlavor("text/unicode");
  1484.     var uri, extra, rSource, rParent, parent;
  1485.     for (var i = 0; i < aDragSession.numDropItems; ++i) {
  1486.       var bestFlavour = {}, dataObj = {}, len = {};
  1487.       aDragSession.getData(trans, i);
  1488.       trans.getAnyTransferData(bestFlavour, dataObj, len);
  1489.       dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
  1490.       if (!dataObj)
  1491.         continue;
  1492.       dataObj = dataObj.data.substring(0, len.value).split("\n");
  1493.       uri     = dataObj[0];
  1494.       if (dataObj.length > 1 && dataObj[1] != "")
  1495.         extra = dataObj[1];
  1496.       else
  1497.         extra = null;
  1498.       switch (bestFlavour.value) {
  1499.       case "moz/rdfitem":
  1500.         rSource = RDF.GetResource(uri);
  1501.         parent  = extra;
  1502.         break;
  1503.       case "text/x-moz-url":
  1504.       case "text/unicode":
  1505.         rSource = BookmarksUtils.createBookmark(null, uri, null, extra, null);
  1506.         parent = null;
  1507.         break;
  1508.       }
  1509.       selection.item.push(rSource);
  1510.       if (parent)
  1511.         rParent = RDF.GetResource(parent);
  1512.       else
  1513.         rParent = null;
  1514.       selection.parent.push(rParent);
  1515.     }
  1516.     selection.length = selection.item.length;
  1517.     BookmarksUtils.checkSelection(selection);
  1518.     return selection;
  1519.   },
  1520.  
  1521.   getTargetFromFolder: function(aResource)
  1522.   {
  1523.     var index = parseInt(this.getProperty(aResource, RDF_NS+"nextVal"));
  1524.     if (isNaN(index))
  1525.       return {parent: null, index: -1};
  1526.     else
  1527.       return {parent: aResource, index: index};
  1528.   },
  1529.  
  1530.   getSelectionFromResource: function (aItem, aParent)
  1531.   {
  1532.     var selection    = {};
  1533.     selection.length = 1;
  1534.     selection.item   = [aItem  ];
  1535.     selection.parent = [aParent];
  1536.     this.checkSelection(selection);
  1537.     return selection;
  1538.   },
  1539.  
  1540.   createBookmark: function (aName, aURL, aCharSet, aDefaultName)
  1541.   {
  1542.     if (!aName) {
  1543.       // look up in the history ds to retrieve the name
  1544.       var rSource = RDF.GetResource(aURL);
  1545.       var HISTDS  = RDF.GetDataSource("rdf:history");
  1546.       var nameArc = RDF.GetResource(NC_NS+"Name");
  1547.       var rName   = HISTDS.GetTarget(rSource, nameArc, true);
  1548.       aName       = rName ? rName.QueryInterface(kRDFLITIID).Value : aDefaultName;
  1549.       if (!aName)
  1550.         aName = aURL;
  1551.     }
  1552.     if (!aCharSet) {
  1553.       var fw = document.commandDispatcher.focusedWindow;
  1554.       if (fw)
  1555.         aCharSet = fw.document.characterSet;
  1556.     }
  1557.     return BMSVC.createBookmark(aName, aURL, null, null, aCharSet, null);
  1558.   },
  1559.  
  1560.   createLivemark: function (aName, aURL, aFeedURL, aDefaultName)
  1561.   {
  1562.     if (!aName) {
  1563.       // look up in the history ds to retrieve the name
  1564.       var rSource = RDF.GetResource(aURL);
  1565.       var HISTDS  = RDF.GetDataSource("rdf:history");
  1566.       var nameArc = RDF.GetResource(NC_NS+"Name");
  1567.       var rName   = HISTDS.GetTarget(rSource, nameArc, true);
  1568.       aName       = rName ? rName.QueryInterface(kRDFLITIID).Value : aDefaultName;
  1569.       if (!aName)
  1570.         aName = aURL;
  1571.     }
  1572.     return BMSVC.createLivemark(aName, aURL, aFeedURL, null);
  1573.   },
  1574.  
  1575.   flushDataSource: function ()
  1576.   {
  1577.     var remoteDS = BMDS.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
  1578.     setTimeout(function () {remoteDS.Flush()}, 100);
  1579.   },
  1580.  
  1581.   // should update the caller, aShowDialog is no more necessary
  1582.   addBookmark: function (aURL, aTitle, aCharset, aIsWebPanel)
  1583.   {
  1584.     openDialog("chrome://browser/content/bookmarks/addBookmark2.xul", "",
  1585.                "centerscreen,chrome,dialog,resizable,dependent", aTitle, aURL, null, aCharset,
  1586.                null, null, aIsWebPanel);
  1587.   },
  1588.  
  1589.   addLivemark: function (aURL, aFeedURL, aTitle)
  1590.   {
  1591.     openDialog("chrome://browser/content/bookmarks/addBookmark2.xul", "",
  1592.                "centerscreen,chrome,dialog,resizable,dependent", aTitle, aURL, null, null,
  1593.                null, null, false, null, null, null, aFeedURL);
  1594.   },
  1595.  
  1596.   loadFavIcon: function (aURL, aFavIconURL) {
  1597.     var urlLiteral = RDF.GetLiteral(aURL);
  1598.     // don't do anything if this URI isn't bookmarked
  1599.     var bmResources = BMSVC.GetSources(RDF.GetResource(NC_NS+"URL"), urlLiteral, true);
  1600.     var toUpdate = 0;
  1601.  
  1602.     while (bmResources.hasMoreElements()) {
  1603.       var bmResource = bmResources.getNext();
  1604.  
  1605.       // don't flag this as needing update if it already has a data: icon url set
  1606.       var oldIcon = BMDS.GetTarget(bmResource, RDF.GetResource(NC_NS+"Icon"), true);
  1607.       if (oldIcon && (oldIcon.QueryInterface(kRDFLITIID).Value.substring(0,5) == "data:"))
  1608.         continue;
  1609.  
  1610.       toUpdate++;
  1611.     }
  1612.  
  1613.     if (toUpdate == 0)
  1614.       return;
  1615.  
  1616.     var chan = IOSVC.newChannel(aFavIconURL, null, null);
  1617.     var listener = new bookmarksFavIconLoadListener (aURL, aFavIconURL, chan);
  1618.     chan.notificationCallbacks = listener;
  1619.     chan.asyncOpen(listener, null);
  1620.   }
  1621. }
  1622.  
  1623. function BookmarkTransaction()
  1624. {
  1625. }
  1626.  
  1627. BookmarkTransaction.prototype = {
  1628.   BATCH_LIMIT : 4,
  1629.   RDFC        : null,
  1630.   BMDS        : null,
  1631.  
  1632.   QueryInterface: function (iid)
  1633.   {
  1634.     if (!iid.equals(Components.interfaces.nsITransaction) &&
  1635.         !iid.equals(Components.interfaces.nsISupports))
  1636.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1637.  
  1638.     return this;
  1639.   },
  1640.  
  1641.   beginUpdateBatch: function()
  1642.   {
  1643.     if (this.item.length > this.BATCH_LIMIT) {
  1644.       this.BMDS.beginUpdateBatch();
  1645.     }
  1646.   },
  1647.  
  1648.   endUpdateBatch: function()
  1649.   {
  1650.     if (this.item.length > this.BATCH_LIMIT) {
  1651.       this.BMDS.endUpdateBatch();
  1652.     }
  1653.   },
  1654.   merge               : function (aTxn)   {return false},
  1655.   getHelperForLanguage: function (aCount) {return null},
  1656.   getInterfaces       : function (aCount) {return null},
  1657.   canCreateWrapper    : function (aIID)   {return "AllAccess"}
  1658. }
  1659.  
  1660. function BookmarkInsertTransaction (aAction)
  1661. {
  1662.   this.wrappedJSObject = this;
  1663.   this.type    = "insert";
  1664.   this.action  = aAction;
  1665.   this.item    = null;
  1666.   this.parent  = null;
  1667.   this.index   = null;
  1668. }
  1669.  
  1670. BookmarkInsertTransaction.prototype =
  1671. {
  1672.   __proto__: BookmarkTransaction.prototype,
  1673.  
  1674.   isTransient: false,
  1675.  
  1676.   doTransaction: function ()
  1677.   {
  1678.     this.beginUpdateBatch();
  1679.     for (var i=0; i<this.item.length; ++i) {
  1680.       this.RDFC.Init(this.BMDS, this.parent[i]);
  1681.       // if the index is -1, we use appendElement, and then update the
  1682.       // index so that undoTransaction can still function
  1683.       if (this.index[i] == -1) {
  1684.         this.RDFC.AppendElement(this.item[i]);
  1685.         this.index[i] = this.RDFC.GetCount();
  1686.       } else {
  1687.         this.RDFC.InsertElementAt(this.item[i], this.index[i], true);
  1688.       }
  1689.     }
  1690.     this.endUpdateBatch();
  1691.   },
  1692.  
  1693.   undoTransaction: function ()
  1694.   {
  1695.     this.beginUpdateBatch();
  1696.     // XXXvarga Can't use |RDFC| here because it's being "reused" elsewhere.
  1697.     var container = Components.classes[kRDFCContractID].createInstance(kRDFCIID);
  1698.     for (var i=this.item.length-1; i>=0; i--) {
  1699.       container.Init(this.BMDS, this.parent[i]);
  1700.       container.RemoveElementAt(this.index[i], true);
  1701.     }
  1702.     this.endUpdateBatch();
  1703.   },
  1704.    
  1705.   redoTransaction: function ()
  1706.   {
  1707.     this.doTransaction();
  1708.   }
  1709. }
  1710.  
  1711. function BookmarkRemoveTransaction (aAction)
  1712. {
  1713.   this.wrappedJSObject = this;
  1714.   this.type    = "remove";
  1715.   this.action  = aAction;
  1716.   this.item    = null;
  1717.   this.parent  = null;
  1718.   this.index   = null;
  1719. }
  1720.  
  1721. BookmarkRemoveTransaction.prototype =
  1722. {
  1723.   __proto__: BookmarkTransaction.prototype,
  1724.  
  1725.   isTransient: false,
  1726.  
  1727.   doTransaction: function ()
  1728.   {
  1729.     this.beginUpdateBatch();
  1730.     for (var i=0; i<this.item.length; ++i) {
  1731.       this.RDFC.Init(this.BMDS, this.parent[i]);
  1732.       this.RDFC.RemoveElementAt(this.index[i], false);
  1733.     }
  1734.     this.endUpdateBatch();
  1735.   },
  1736.  
  1737.   undoTransaction: function ()
  1738.   {
  1739.     this.beginUpdateBatch();
  1740.     for (var i=this.item.length-1; i>=0; i--) {
  1741.       this.RDFC.Init(this.BMDS, this.parent[i]);
  1742.       this.RDFC.InsertElementAt(this.item[i], this.index[i], false);
  1743.     }
  1744.     this.endUpdateBatch();
  1745.   },
  1746.    
  1747.   redoTransaction: function ()
  1748.   {
  1749.     this.doTransaction();
  1750.   }
  1751. }
  1752.  
  1753. function BookmarkMoveTransaction (aAction, aSelection, aTarget)
  1754. {
  1755.   this.wrappedJSObject = this;
  1756.   this.type      = "move";
  1757.   this.action    = aAction;
  1758.   this.selection = aSelection;
  1759.   this.target    = aTarget;
  1760. }
  1761.  
  1762. BookmarkMoveTransaction.prototype =
  1763. {
  1764.   __proto__: BookmarkTransaction.prototype,
  1765.  
  1766.   isTransient: false,
  1767.  
  1768.   beginUpdateBatch: function()
  1769.   {
  1770.     if (this.selection.length > this.BATCH_LIMIT) {
  1771.       this.BMDS.beginUpdateBatch();
  1772.     }
  1773.   },
  1774.  
  1775.   endUpdateBatch: function()
  1776.   {
  1777.     if (this.selection.length > this.BATCH_LIMIT) {
  1778.       this.BMDS.endUpdateBatch();
  1779.     }
  1780.   },
  1781.  
  1782.   doTransaction: function ()
  1783.   {
  1784.     this.beginUpdateBatch();
  1785.     BookmarksUtils.removeSelection("move", this.selection);
  1786.     BookmarksUtils.insertSelection("move", this.selection, this.target);
  1787.     this.endUpdateBatch();
  1788.   },
  1789.  
  1790.   undoTransaction: function () {},
  1791.   redoTransaction: function () {}
  1792. }
  1793.  
  1794. function BookmarkImportTransaction (aAction)
  1795. {
  1796.   this.wrappedJSObject = this;
  1797.   this.type    = "import";
  1798.   this.action  = aAction;
  1799.   this.item    = [];
  1800.   this.parent  = [];
  1801.   this.index   = [];
  1802. }
  1803.  
  1804. BookmarkImportTransaction.prototype =
  1805. {
  1806.   __proto__: BookmarkTransaction.prototype,
  1807.  
  1808.   isTransient: false,
  1809.  
  1810.   doTransaction: function ()
  1811.   {
  1812.   },
  1813.  
  1814.   undoTransaction: function ()
  1815.   {
  1816.     this.beginUpdateBatch();
  1817.     for (var i=this.item.length-1; i>=0; i--) {
  1818.       this.RDFC.Init(this.BMDS, this.parent[i]);
  1819.       this.RDFC.RemoveElementAt(this.index[i], true);
  1820.     }
  1821.     this.endUpdateBatch();
  1822.   },
  1823.    
  1824.   redoTransaction: function ()
  1825.   {
  1826.     this.beginUpdateBatch();
  1827.     for (var i=0; i<this.item.length; ++i) {
  1828.       this.RDFC.Init(this.BMDS, this.parent[i]);
  1829.       this.RDFC.InsertElementAt(this.item[i], this.index[i], true);
  1830.     }
  1831.     this.endUpdateBatch();
  1832.   }
  1833. }
  1834.  
  1835. var BookmarkEditMenuTxnListener =
  1836. {
  1837.  
  1838.   didDo: function (aTxmgr, aTxn)
  1839.   {
  1840.     this.updateMenuItem(aTxmgr, aTxn);
  1841.   },
  1842.  
  1843.   didUndo: function (aTxmgr, aTxn)
  1844.   {
  1845.     this.updateMenuItem(aTxmgr, aTxn);
  1846.   },
  1847.  
  1848.   didRedo: function (aTxmgr, aTxn)
  1849.   {
  1850.     this.updateMenuItem(aTxmgr, aTxn);
  1851.   },
  1852.  
  1853.   didMerge       : function (aTxmgr, aTxn) {},
  1854.   didBeginBatch  : function (aTxmgr, aTxn) {},
  1855.   didEndBatch    : function (aTxmgr, aTxn) {},
  1856.   willDo         : function (aTxmgr, aTxn) {},
  1857.   willUndo       : function (aTxmgr, aTxn) {},
  1858.   willRedo       : function (aTxmgr, aTxn) {},
  1859.   willMerge      : function (aTxmgr, aTxn) {},
  1860.   willBeginBatch : function (aTxmgr, aTxn) {},
  1861.   willEndBatch   : function (aTxmgr, aTxn) {},
  1862.  
  1863.   updateMenuItem: function (aTxmgr, aTxn) {
  1864.     if (aTxn) {
  1865.       aTxn = aTxn.wrappedJSObject;
  1866.       if ((aTxn.type == "remove" || aTxn.type == "insert") && aTxn.action == "move")
  1867.       return;
  1868.     }
  1869.     var node, transactionNumber, transactionList, transactionLabel, action;
  1870.     node = document.getElementById("cmd_undo");
  1871.     transactionNumber = aTxmgr.numberOfUndoItems;
  1872.     dump("N UNDO: "+transactionNumber+"\n")
  1873.     if (transactionNumber == 0) {
  1874.       transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_undo");
  1875.     } else {
  1876.       transactionList  = aTxmgr.getUndoList();
  1877.       action           = transactionList.getItem(transactionNumber-1).wrappedJSObject.action;
  1878.       transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_"+action+"_undo")
  1879.     }
  1880.     node.setAttribute("label", transactionLabel);
  1881.       
  1882.     node = document.getElementById("cmd_redo");
  1883.     transactionNumber = aTxmgr.numberOfRedoItems;
  1884.     dump("N REDO: "+transactionNumber+"\n")
  1885.     if (transactionNumber == 0) {
  1886.       transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_redo");
  1887.     } else {
  1888.       transactionList  = aTxmgr.getRedoList();
  1889.       action           = transactionList.getItem(transactionNumber-1).wrappedJSObject.action;
  1890.       transactionLabel = BookmarksUtils.getLocaleString("cmd_bm_"+action+"_redo")
  1891.     }
  1892.     node.setAttribute("label", transactionLabel);
  1893.   }
  1894. }
  1895.  
  1896. // favicon loaders
  1897.  
  1898. function bookmarksFavIconLoadListener(uri, faviconurl, channel) {
  1899.   this.mURI = uri;
  1900.   this.mFavIconURL = faviconurl;
  1901.   this.mCountRead = 0;
  1902.   this.mChannel = channel;
  1903. }
  1904.  
  1905. bookmarksFavIconLoadListener.prototype = {
  1906.   mURI : null,
  1907.   mFavIconURL : null,
  1908.   mCountRead : null,
  1909.   mChannel : null,
  1910.   mBytes : Array(),
  1911.   mStream : null,
  1912.  
  1913.   QueryInterface: function (iid) {
  1914.     if (!iid.equals(Components.interfaces.nsISupports) &&
  1915.         !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
  1916.         !iid.equals(Components.interfaces.nsIRequestObserver) &&
  1917.         !iid.equals(Components.interfaces.nsIHttpEventSink) &&
  1918.         !iid.equals(Components.interfaces.nsIProgressEventSink) && // see below
  1919.         !iid.equals(Components.interfaces.nsIStreamListener)) {
  1920.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1921.     }
  1922.     return this;
  1923.   },
  1924.  
  1925.   // nsIInterfaceRequestor
  1926.   getInterface: function (iid) {
  1927.     try {
  1928.       return this.QueryInterface(iid);
  1929.     } catch (e) {
  1930.       throw Components.results.NS_NOINTERFACE;
  1931.     }
  1932.   },
  1933.  
  1934.   // nsIRequestObserver
  1935.   onStartRequest : function (aRequest, aContext) {
  1936.     this.mStream = Components.classes['@mozilla.org/binaryinputstream;1'].createInstance(Components.interfaces.nsIBinaryInputStream);
  1937.   },
  1938.  
  1939.   onStopRequest : function (aRequest, aContext, aStatusCode) {
  1940.     var httpChannel = this.mChannel.QueryInterface(Components.interfaces.nsIHttpChannel);
  1941.     if ((httpChannel && httpChannel.requestSucceeded) &&
  1942.         Components.isSuccessCode(aStatusCode) &&
  1943.         this.mCountRead > 0)
  1944.     {
  1945.       var dataurl;
  1946.       // XXX - arbitrary size beyond which we won't store a favicon.  This is /extremely/
  1947.       // generous, and is probably too high.
  1948.       if (this.mCountRead > 16384) {
  1949.         dataurl = "data:";      // hack meaning "pretend this doesn't exist"
  1950.       } else {
  1951.         // get us a mime type for this
  1952.         var mimeType = null;
  1953.  
  1954.         const nsICategoryManager = Components.interfaces.nsICategoryManager;
  1955.         const nsIContentSniffer = Components.interfaces.nsIContentSniffer;
  1956.  
  1957.         var catMgr = Components.classes["@mozilla.org/categorymanager;1"].getService(nsICategoryManager);
  1958.         var sniffers = catMgr.enumerateCategory("content-sniffing-services");
  1959.         while (mimeType == null && sniffers.hasMoreElements()) {
  1960.           var snifferCID = sniffers.getNext().QueryInterface(Components.interfaces.nsISupportsCString).toString();
  1961.           var sniffer = Components.classes[snifferCID].getService(nsIContentSniffer);
  1962.  
  1963.           try {
  1964.             mimeType = sniffer.getMIMETypeFromContent (this.mBytes, this.mCountRead);
  1965.           } catch (e) {
  1966.             mimeType = null;
  1967.             // ignore
  1968.           }
  1969.         }
  1970.       }
  1971.  
  1972.       if (mimeType == null) {
  1973.         BMSVC.updateBookmarkIcon(this.mURI, null, null, 0);
  1974.       } else {
  1975.         BMSVC.updateBookmarkIcon(this.mURI, mimeType, this.mBytes, this.mCountRead);
  1976.       }
  1977.     }
  1978.  
  1979.     this.mChannel = null;
  1980.   },
  1981.  
  1982.   // nsIStreamObserver
  1983.   onDataAvailable : function (aRequest, aContext, aInputStream, aOffset, aCount) {
  1984.     // we could get a different aInputStream, so we don't save this;
  1985.     // it's unlikely we'll get more than one onDataAvailable for a
  1986.     // favicon anyway
  1987.     this.mStream.setInputStream(aInputStream);
  1988.  
  1989.     var chunk = this.mStream.readByteArray(aCount);
  1990.     this.mBytes = this.mBytes.concat(chunk);
  1991.     this.mCountRead += aCount;
  1992.   },
  1993.  
  1994.   // nsIHttpEventSink
  1995.   onRedirect : function (aHttpChannel, aNewChannel) {
  1996.     this.mChannel = aNewChannel;
  1997.   },
  1998.  
  1999.   // nsIProgressEventSink: the only reason we support
  2000.   // nsIProgressEventSink is to shut up a whole slew of xpconnect
  2001.   // warnings in debug builds.  (see bug #253127)
  2002.   onProgress : function (aRequest, aContext, aProgress, aProgressMax) { },
  2003.   onStatus : function (aRequest, aContext, aStatus, aStatusArg) { }
  2004. }
  2005.  
  2006.