home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / xulrunner-1.9.0.14 / chrome / toolkit.jar / content / mozapps / downloads / downloads.js < prev    next >
Encoding:
Text File  |  2008-05-06  |  40.0 KB  |  1,308 lines

  1. //@line 44 "/build/buildd/xulrunner-1.9-1.9.0.14+build2+nobinonly/mozilla/toolkit/mozapps/downloads/content/downloads.js"
  2.  
  3. ////////////////////////////////////////////////////////////////////////////////
  4. //// Globals
  5.  
  6. const PREF_BDM_CLOSEWHENDONE = "browser.download.manager.closeWhenDone";
  7. const PREF_BDM_ALERTONEXEOPEN = "browser.download.manager.alertOnEXEOpen";
  8.  
  9. const nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
  10.                                            "nsILocalFile", "initWithPath");
  11.  
  12. var Cc = Components.classes;
  13. var Ci = Components.interfaces;
  14. let Cu = Components.utils;
  15. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  16. Cu.import("resource://gre/modules/DownloadUtils.jsm");
  17. Cu.import("resource://gre/modules/PluralForm.jsm");
  18.  
  19. const nsIDM = Ci.nsIDownloadManager;
  20.  
  21. let gDownloadManager = Cc["@mozilla.org/download-manager;1"].getService(nsIDM);
  22. let gDownloadListener = null;
  23. let gDownloadsView = null;
  24. let gSearchBox = null;
  25. let gSearchTerms = [];
  26. let gBuilder = 0;
  27.  
  28. // This variable is used when performing commands on download items and gives
  29. // the command the ability to do something after all items have been operated
  30. // on. The following convention is used to handle the value of the variable:
  31. // whenever we aren't performing a command, the value is |undefined|; just
  32. // before executing commands, the value will be set to |null|; and when
  33. // commands want to create a callback, they set the value to be a callback
  34. // function to be executed after all download items have been visited.
  35. let gPerformAllCallback;
  36.  
  37. // Control the performance of the incremental list building by setting how many
  38. // milliseconds to wait before building more of the list and how many items to
  39. // add between each delay.
  40. const gListBuildDelay = 300;
  41. const gListBuildChunk = 3;
  42.  
  43. // Array of download richlistitem attributes to check when searching
  44. const gSearchAttributes = [
  45.   "target",
  46.   "status",
  47.   "dateTime",
  48. ];
  49.  
  50. // If the user has interacted with the window in a significant way, we should
  51. // not auto-close the window. Tough UI decisions about what is "significant."
  52. var gUserInteracted = false;
  53.  
  54. // These strings will be converted to the corresponding ones from the string
  55. // bundle on startup.
  56. let gStr = {
  57.   paused: "paused",
  58.   cannotPause: "cannotPause",
  59.   doneStatus: "doneStatus",
  60.   doneSize: "doneSize",
  61.   doneSizeUnknown: "doneSizeUnknown",
  62.   stateFailed: "stateFailed",
  63.   stateCanceled: "stateCanceled",
  64.   stateBlockedParentalControls: "stateBlocked",
  65.   stateBlockedPolicy: "stateBlockedPolicy",
  66.   stateDirty: "stateDirty",
  67.   yesterday: "yesterday",
  68.   monthDate: "monthDate",
  69.   downloadsTitleFiles: "downloadsTitleFiles",
  70.   downloadsTitlePercent: "downloadsTitlePercent",
  71.   fileExecutableSecurityWarningTitle: "fileExecutableSecurityWarningTitle",
  72.   fileExecutableSecurityWarningDontAsk: "fileExecutableSecurityWarningDontAsk"
  73. };
  74.  
  75. // The statement to query for downloads that are active or match the search
  76. let gStmt = gDownloadManager.DBConnection.createStatement(
  77.   "SELECT id, target, name, source, state, startTime, endTime, referrer, " +
  78.          "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " +
  79.   "FROM moz_downloads " +
  80.   "ORDER BY isActive DESC, endTime DESC, startTime DESC");
  81.  
  82. ////////////////////////////////////////////////////////////////////////////////
  83. //// Utility Functions
  84.  
  85. function getDownload(aID)
  86. {
  87.   return document.getElementById("dl" + aID);
  88. }
  89.  
  90. ////////////////////////////////////////////////////////////////////////////////
  91. //// Start/Stop Observers
  92.  
  93. function downloadCompleted(aDownload)
  94. {
  95.   // The download is changing state, so update the clear list button
  96.   updateClearListButton();
  97.  
  98.   // Wrap this in try...catch since this can be called while shutting down...
  99.   // it doesn't really matter if it fails then since well.. we're shutting down
  100.   // and there's no UI to update!
  101.   try {
  102.     let dl = getDownload(aDownload.id);
  103.  
  104.     // Update attributes now that we've finished
  105.     dl.setAttribute("startTime", Math.round(aDownload.startTime / 1000));
  106.     dl.setAttribute("endTime", Date.now());
  107.     dl.setAttribute("currBytes", aDownload.amountTransferred);
  108.     dl.setAttribute("maxBytes", aDownload.size);
  109.  
  110.     // Move the download below active if it should stay in the list
  111.     if (downloadMatchesSearch(dl)) {
  112.       // Iterate down until we find a non-active download
  113.       let next = dl.nextSibling;
  114.       while (next && next.inProgress)
  115.         next = next.nextSibling;
  116.  
  117.       // Move the item and color everything after where it moved from
  118.       let fixup = dl.nextSibling;
  119.       gDownloadsView.insertBefore(dl, next);
  120.       stripeifyList(fixup);
  121.     } else {
  122.       removeFromView(dl);
  123.     }
  124.  
  125.     // getTypeFromFile fails if it can't find a type for this file.
  126.     try {
  127.       // Refresh the icon, so that executable icons are shown.
  128.       var mimeService = Cc["@mozilla.org/mime;1"].
  129.                         getService(Ci.nsIMIMEService);
  130.       var contentType = mimeService.getTypeFromFile(aDownload.targetFile);
  131.  
  132.       var listItem = getDownload(aDownload.id)
  133.       var oldImage = listItem.getAttribute("image");
  134.       // Tacking on contentType bypasses cache
  135.       listItem.setAttribute("image", oldImage + "&contentType=" + contentType);
  136.     } catch (e) { }
  137.  
  138.     if (gDownloadManager.activeDownloadCount == 0)
  139.       document.title = document.documentElement.getAttribute("statictitle");
  140.   }
  141.   catch (e) { }
  142. }
  143.  
  144. function autoRemoveAndClose(aDownload)
  145. {
  146.   var pref = Cc["@mozilla.org/preferences-service;1"].
  147.              getService(Ci.nsIPrefBranch);
  148.  
  149.   if (gDownloadManager.activeDownloadCount == 0) {
  150.     // For the moment, just use the simple heuristic that if this window was
  151.     // opened by the download process, rather than by the user, it should
  152.     // auto-close if the pref is set that way. If the user opened it themselves,
  153.     // it should not close until they explicitly close it.  Additionally, the
  154.     // preference to control the feature may not be set, so defaulting to
  155.     // keeping the window open.
  156.     let autoClose = false;
  157.     try {
  158.       autoClose = pref.getBoolPref(PREF_BDM_CLOSEWHENDONE);
  159.     } catch (e) { }
  160.     var autoOpened =
  161.       !window.opener || window.opener.location.href == window.location.href;
  162.     if (autoClose && autoOpened && !gUserInteracted) {
  163.       gCloseDownloadManager();
  164.       return true;
  165.     }
  166.   }
  167.  
  168.   return false;
  169. }
  170.  
  171. // This function can be overwritten by extensions that wish to place the
  172. // Download Window in another part of the UI.
  173. function gCloseDownloadManager()
  174. {
  175.   window.close();
  176. }
  177.  
  178. ////////////////////////////////////////////////////////////////////////////////
  179. //// Download Event Handlers
  180.  
  181. function cancelDownload(aDownload)
  182. {
  183.   gDownloadManager.cancelDownload(aDownload.getAttribute("dlid"));
  184.  
  185.   // XXXben -
  186.   // If we got here because we resumed the download, we weren't using a temp file
  187.   // because we used saveURL instead. (this is because the proper download mechanism
  188.   // employed by the helper app service isn't fully accessible yet... should be fixed...
  189.   // talk to bz...)
  190.   // the upshot is we have to delete the file if it exists.
  191.   var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
  192.  
  193.   if (f.exists())
  194.     f.remove(false);
  195. }
  196.  
  197. function pauseDownload(aDownload)
  198. {
  199.   var id = aDownload.getAttribute("dlid");
  200.   gDownloadManager.pauseDownload(id);
  201. }
  202.  
  203. function resumeDownload(aDownload)
  204. {
  205.   gDownloadManager.resumeDownload(aDownload.getAttribute("dlid"));
  206. }
  207.  
  208. function removeDownload(aDownload)
  209. {
  210.   gDownloadManager.removeDownload(aDownload.getAttribute("dlid"));
  211. }
  212.  
  213. function retryDownload(aDownload)
  214. {
  215.   removeFromView(aDownload);
  216.   gDownloadManager.retryDownload(aDownload.getAttribute("dlid"));
  217. }
  218.  
  219. function showDownload(aDownload)
  220. {
  221.   var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
  222.  
  223.   try {
  224.     // Show the directory containing the file and select the file
  225.     f.reveal();
  226.   } catch (e) {
  227.     // If reveal fails for some reason (e.g., it's not implemented on unix or
  228.     // the file doesn't exist), try using the parent if we have it.
  229.     let parent = f.parent.QueryInterface(Ci.nsILocalFile);
  230.     if (!parent)
  231.       return;
  232.  
  233.     try {
  234.       // "Double click" the parent directory to show where the file should be
  235.       parent.launch();
  236.     } catch (e) {
  237.       // If launch also fails (probably because it's not implemented), let the
  238.       // OS handler try to open the parent
  239.       openExternal(parent);
  240.     }
  241.   }
  242. }
  243.  
  244. function onDownloadDblClick(aEvent)
  245. {
  246.   // Only do the default action for double primary clicks
  247.   if (aEvent.button == 0)
  248.     doDefaultForSelected();
  249. }
  250.  
  251. function openDownload(aDownload)
  252. {
  253.   var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
  254.   if (f.isExecutable()) {
  255.     var dontAsk = false;
  256.     var pref = Cc["@mozilla.org/preferences-service;1"].
  257.                getService(Ci.nsIPrefBranch);
  258.     try {
  259.       dontAsk = !pref.getBoolPref(PREF_BDM_ALERTONEXEOPEN);
  260.     } catch (e) { }
  261.  
  262.     if (!dontAsk) {
  263.       var strings = document.getElementById("downloadStrings");
  264.       var name = aDownload.getAttribute("target");
  265.       var message = strings.getFormattedString("fileExecutableSecurityWarning", [name, name]);
  266.  
  267.       let title = gStr.fileExecutableSecurityWarningTitle;
  268.       let dontAsk = gStr.fileExecutableSecurityWarningDontAsk;
  269.  
  270.       var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  271.                       getService(Ci.nsIPromptService);
  272.       var checkbox = { value: false };
  273.       var open = promptSvc.confirmCheck(window, title, message, dontAsk, checkbox);
  274.  
  275.       if (!open)
  276.         return;
  277.       pref.setBoolPref(PREF_BDM_ALERTONEXEOPEN, !checkbox.value);
  278.     }
  279.   }
  280.   try {
  281.     f.launch();
  282.   } catch (ex) {
  283.     // if launch fails, try sending it through the system's external
  284.     // file: URL handler
  285.     openExternal(f);
  286.   }
  287. }
  288.  
  289. function openReferrer(aDownload)
  290. {
  291.   openURL(getReferrerOrSource(aDownload));
  292. }
  293.  
  294. function copySourceLocation(aDownload)
  295. {
  296.   var uri = aDownload.getAttribute("uri");
  297.   var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
  298.                   getService(Ci.nsIClipboardHelper);
  299.  
  300.   // Check if we should initialize a callback
  301.   if (gPerformAllCallback === null) {
  302.     let uris = [];
  303.     gPerformAllCallback = function(aURI) aURI ? uris.push(aURI) :
  304.       clipboard.copyString(uris.join("\n"));
  305.   }
  306.  
  307.   // We have a callback to use, so use it to add a uri
  308.   if (typeof gPerformAllCallback == "function")
  309.     gPerformAllCallback(uri);
  310.   else {
  311.     // It's a plain copy source, so copy it
  312.     clipboard.copyString(uri);
  313.   }
  314. }
  315.  
  316. /**
  317.  * Remove the currently shown downloads from the download list.
  318.  */
  319. function clearDownloadList() {
  320.   // Clear the whole list if there's no search
  321.   if (gSearchTerms == "") {
  322.     gDownloadManager.cleanUp();
  323.     return;
  324.   }
  325.  
  326.   // Remove each download starting from the end until we hit a download
  327.   // that is in progress
  328.   let item;
  329.   while ((item = gDownloadsView.lastChild) && !item.inProgress)
  330.     removeDownload(item);
  331.  
  332.   // Clear the input as if the user did it and move focus to the list
  333.   gSearchBox.value = "";
  334.   gSearchBox.doCommand();
  335.   gDownloadsView.focus();
  336. }
  337.  
  338. // This is called by the progress listener.
  339. var gLastComputedMean = -1;
  340. var gLastActiveDownloads = 0;
  341. function onUpdateProgress()
  342. {
  343.   let numActiveDownloads = gDownloadManager.activeDownloadCount;
  344.  
  345.   // Use the default title and reset "last" values if there's no downloads
  346.   if (numActiveDownloads == 0) {
  347.     document.title = document.documentElement.getAttribute("statictitle");
  348.     gLastComputedMean = -1;
  349.     gLastActiveDownloads = 0;
  350.  
  351.     return;
  352.   }
  353.  
  354.   // Establish the mean transfer speed and amount downloaded.
  355.   var mean = 0;
  356.   var base = 0;
  357.   var dls = gDownloadManager.activeDownloads;
  358.   while (dls.hasMoreElements()) {
  359.     let dl = dls.getNext().QueryInterface(Ci.nsIDownload);
  360.     if (dl.percentComplete < 100 && dl.size > 0) {
  361.       mean += dl.amountTransferred;
  362.       base += dl.size;
  363.     }
  364.   }
  365.  
  366.   // Calculate the percent transferred, unless we don't have a total file size
  367.   let title = gStr.downloadsTitlePercent;
  368.   if (base == 0)
  369.     title = gStr.downloadsTitleFiles;
  370.   else
  371.     mean = Math.floor((mean / base) * 100);
  372.  
  373.   // Update title of window
  374.   if (mean != gLastComputedMean || gLastActiveDownloads != numActiveDownloads) {
  375.     gLastComputedMean = mean;
  376.     gLastActiveDownloads = numActiveDownloads;
  377.  
  378.     // Get the correct plural form and insert number of downloads and percent
  379.     title = PluralForm.get(numActiveDownloads, title);
  380.     title = replaceInsert(title, 1, numActiveDownloads);
  381.     title = replaceInsert(title, 2, mean);
  382.  
  383.     document.title = title;
  384.   }
  385. }
  386.  
  387. ////////////////////////////////////////////////////////////////////////////////
  388. //// Startup, Shutdown
  389.  
  390. function Startup()
  391. {
  392.   gDownloadsView = document.getElementById("downloadView");
  393.   gSearchBox = document.getElementById("searchbox");
  394.  
  395.   // convert strings to those in the string bundle
  396.   let (sb = document.getElementById("downloadStrings")) {
  397.     let getStr = function(string) sb.getString(string);
  398.     for (let [name, value] in Iterator(gStr))
  399.       gStr[name] = typeof value == "string" ? getStr(value) : value.map(getStr);
  400.   }
  401.  
  402.   buildDownloadList(true);
  403.  
  404.   // The DownloadProgressListener (DownloadProgressListener.js) handles progress
  405.   // notifications.
  406.   gDownloadListener = new DownloadProgressListener();
  407.   gDownloadManager.addListener(gDownloadListener);
  408.  
  409.   // If the UI was displayed because the user interacted, we need to make sure
  410.   // we update gUserInteracted accordingly.
  411.   if (window.arguments[1] == Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED)
  412.     gUserInteracted = true;
  413.  
  414.   // downloads can finish before Startup() does, so check if the window should
  415.   // close and act accordingly
  416.   if (!autoRemoveAndClose())
  417.     gDownloadsView.focus();
  418.  
  419.   let obs = Cc["@mozilla.org/observer-service;1"].
  420.             getService(Ci.nsIObserverService);
  421.   obs.addObserver(gDownloadObserver, "download-manager-remove-download", false);
  422.  
  423.   // Clear the search box and move focus to the list on escape from the box
  424.   gSearchBox.addEventListener("keypress", function(e) {
  425.     if (e.keyCode == e.DOM_VK_ESCAPE) {
  426.       // Clear the input as if the user did it
  427.       gSearchBox.value = "";
  428.       gSearchBox.doCommand();
  429.  
  430.       // Move focus to the list instead of closing the window
  431.       gDownloadsView.focus();
  432.       e.preventDefault();
  433.     }
  434.   }, true);
  435. }
  436.  
  437. function Shutdown()
  438. {
  439.   gDownloadManager.removeListener(gDownloadListener);
  440.  
  441.   let obs = Cc["@mozilla.org/observer-service;1"].
  442.             getService(Ci.nsIObserverService);
  443.   obs.removeObserver(gDownloadObserver, "download-manager-remove-download");
  444.  
  445.   clearTimeout(gBuilder);
  446.   gStmt.reset();
  447.   gStmt.finalize();
  448. }
  449.  
  450. let gDownloadObserver = {
  451.   observe: function gdo_observe(aSubject, aTopic, aData) {
  452.     switch (aTopic) {
  453.       case "download-manager-remove-download":
  454.         // A null subject here indicates "remove all"
  455.         if (!aSubject) {
  456.           // Rebuild the default view
  457.           buildDownloadList(true);
  458.           break;
  459.         }
  460.  
  461.         // Otherwise, remove a single download
  462.         let id = aSubject.QueryInterface(Ci.nsISupportsPRUint32);
  463.         let dl = getDownload(id.data);
  464.         removeFromView(dl);
  465.         break;
  466.     }
  467.   }
  468. };
  469.  
  470. ////////////////////////////////////////////////////////////////////////////////
  471. //// View Context Menus
  472.  
  473. var gContextMenus = [
  474.   // DOWNLOAD_DOWNLOADING
  475.   [
  476.     "menuitem_pause"
  477.     , "menuitem_cancel"
  478.     , "menuseparator"
  479.     , "menuitem_show"
  480.     , "menuseparator"
  481.     , "menuitem_openReferrer"
  482.     , "menuitem_copyLocation"
  483.     , "menuseparator"
  484.     , "menuitem_selectAll"
  485.   ],
  486.   // DOWNLOAD_FINISHED
  487.   [
  488.     "menuitem_open"
  489.     , "menuitem_show"
  490.     , "menuseparator"
  491.     , "menuitem_openReferrer"
  492.     , "menuitem_copyLocation"
  493.     , "menuseparator"
  494.     , "menuitem_selectAll"
  495.     , "menuseparator"
  496.     , "menuitem_removeFromList"
  497.   ],
  498.   // DOWNLOAD_FAILED
  499.   [
  500.     "menuitem_retry"
  501.     , "menuseparator"
  502.     , "menuitem_openReferrer"
  503.     , "menuitem_copyLocation"
  504.     , "menuseparator"
  505.     , "menuitem_selectAll"
  506.     , "menuseparator"
  507.     , "menuitem_removeFromList"
  508.   ],
  509.   // DOWNLOAD_CANCELED
  510.   [
  511.     "menuitem_retry"
  512.     , "menuseparator"
  513.     , "menuitem_openReferrer"
  514.     , "menuitem_copyLocation"
  515.     , "menuseparator"
  516.     , "menuitem_selectAll"
  517.     , "menuseparator"
  518.     , "menuitem_removeFromList"
  519.   ],
  520.   // DOWNLOAD_PAUSED
  521.   [
  522.     "menuitem_resume"
  523.     , "menuitem_cancel"
  524.     , "menuseparator"
  525.     , "menuitem_show"
  526.     , "menuseparator"
  527.     , "menuitem_openReferrer"
  528.     , "menuitem_copyLocation"
  529.     , "menuseparator"
  530.     , "menuitem_selectAll"
  531.   ],
  532.   // DOWNLOAD_QUEUED
  533.   [
  534.     "menuitem_cancel"
  535.     , "menuseparator"
  536.     , "menuitem_show"
  537.     , "menuseparator"
  538.     , "menuitem_openReferrer"
  539.     , "menuitem_copyLocation"
  540.     , "menuseparator"
  541.     , "menuitem_selectAll"
  542.   ],
  543.   // DOWNLOAD_BLOCKED_PARENTAL
  544.   [
  545.     "menuitem_openReferrer"
  546.     , "menuitem_copyLocation"
  547.     , "menuseparator"
  548.     , "menuitem_selectAll"
  549.     , "menuseparator"
  550.     , "menuitem_removeFromList"
  551.   ],
  552.   // DOWNLOAD_SCANNING
  553.   [
  554.     "menuitem_show"
  555.     , "menuseparator"
  556.     , "menuitem_openReferrer"
  557.     , "menuitem_copyLocation"
  558.     , "menuseparator"
  559.     , "menuitem_selectAll"
  560.   ],
  561.   // DOWNLOAD_DIRTY
  562.   [
  563.     "menuitem_openReferrer"
  564.     , "menuitem_copyLocation"
  565.     , "menuseparator"
  566.     , "menuitem_selectAll"
  567.     , "menuseparator"
  568.     , "menuitem_removeFromList"
  569.   ],
  570.   // DOWNLOAD_BLOCKED_POLICY
  571.   [
  572.     "menuitem_openReferrer"
  573.     , "menuitem_copyLocation"
  574.     , "menuseparator"
  575.     , "menuitem_selectAll"
  576.     , "menuseparator"
  577.     , "menuitem_removeFromList"
  578.   ]
  579. ];
  580.  
  581. function buildContextMenu(aEvent)
  582. {
  583.   if (aEvent.target.id != "downloadContextMenu")
  584.     return false;
  585.  
  586.   var popup = document.getElementById("downloadContextMenu");
  587.   while (popup.hasChildNodes())
  588.     popup.removeChild(popup.firstChild);
  589.  
  590.   if (gDownloadsView.selectedItem) {
  591.     let dl = gDownloadsView.selectedItem;
  592.     let idx = parseInt(dl.getAttribute("state"));
  593.     if (idx < 0)
  594.       idx = 0;
  595.  
  596.     var menus = gContextMenus[idx];
  597.     for (let i = 0; i < menus.length; ++i) {
  598.       let menuitem = document.getElementById(menus[i]).cloneNode(true);
  599.       let cmd = menuitem.getAttribute("cmd");
  600.       if (cmd)
  601.         menuitem.disabled = !gDownloadViewController.isCommandEnabled(cmd, dl);
  602.  
  603.       popup.appendChild(menuitem);
  604.     }
  605.  
  606.     return true;
  607.   }
  608.  
  609.   return false;
  610. }
  611.  
  612. ////////////////////////////////////////////////////////////////////////////////
  613. //// Drag and Drop
  614.  
  615. var gDownloadDNDObserver =
  616. {
  617.   onDragOver: function (aEvent, aFlavour, aDragSession)
  618.   {
  619.     aDragSession.canDrop = true;
  620.   },
  621.  
  622.   onDrop: function(aEvent, aXferData, aDragSession)
  623.   {
  624.     var split = aXferData.data.split("\n");
  625.     var url = split[0];
  626.     if (url != aXferData.data) {  //do nothing, not a valid URL
  627.       var name = split[1];
  628.       saveURL(url, name, null, true, true);
  629.     }
  630.   },
  631.   _flavourSet: null,
  632.   getSupportedFlavours: function ()
  633.   {
  634.     if (!this._flavourSet) {
  635.       this._flavourSet = new FlavourSet();
  636.       this._flavourSet.appendFlavour("text/x-moz-url");
  637.       this._flavourSet.appendFlavour("text/unicode");
  638.     }
  639.     return this._flavourSet;
  640.   }
  641. }
  642.  
  643. ////////////////////////////////////////////////////////////////////////////////
  644. //// Command Updating and Command Handlers
  645.  
  646. var gDownloadViewController = {
  647.   isCommandEnabled: function(aCommand, aItem)
  648.   {
  649.     let dl = aItem;
  650.     let download = null; // used for getting an nsIDownload object
  651.  
  652.     switch (aCommand) {
  653.       case "cmd_cancel":
  654.         return dl.inProgress;
  655.       case "cmd_open": {
  656.         let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file"));
  657.         return dl.openable && file.exists();
  658.       }
  659.       case "cmd_show": {
  660.         let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file"));
  661.         return file.exists();
  662.       }
  663.       case "cmd_pause":
  664.         download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
  665.         return dl.inProgress && !dl.paused && download.resumable;
  666.       case "cmd_pauseResume":
  667.         download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
  668.         return (dl.inProgress || dl.paused) && download.resumable;
  669.       case "cmd_resume":
  670.         download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
  671.         return dl.paused && download.resumable;
  672.       case "cmd_openReferrer":
  673.         return dl.hasAttribute("referrer");
  674.       case "cmd_removeFromList":
  675.       case "cmd_retry":
  676.         return dl.removable;
  677.       case "cmd_copyLocation":
  678.         return true;
  679.     }
  680.     return false;
  681.   },
  682.  
  683.   doCommand: function(aCommand, aItem)
  684.   {
  685.     if (this.isCommandEnabled(aCommand, aItem))
  686.       this.commands[aCommand](aItem);
  687.   },
  688.  
  689.   commands: {
  690.     cmd_cancel: function(aSelectedItem) {
  691.       cancelDownload(aSelectedItem);
  692.     },
  693.     cmd_open: function(aSelectedItem) {
  694.       openDownload(aSelectedItem);
  695.     },
  696.     cmd_openReferrer: function(aSelectedItem) {
  697.       openReferrer(aSelectedItem);
  698.     },
  699.     cmd_pause: function(aSelectedItem) {
  700.       pauseDownload(aSelectedItem);
  701.     },
  702.     cmd_pauseResume: function(aSelectedItem) {
  703.       if (aSelectedItem.paused)
  704.         this.cmd_resume(aSelectedItem);
  705.       else
  706.         this.cmd_pause(aSelectedItem);
  707.     },
  708.     cmd_removeFromList: function(aSelectedItem) {
  709.       removeDownload(aSelectedItem);
  710.     },
  711.     cmd_resume: function(aSelectedItem) {
  712.       resumeDownload(aSelectedItem);
  713.     },
  714.     cmd_retry: function(aSelectedItem) {
  715.       retryDownload(aSelectedItem);
  716.     },
  717.     cmd_show: function(aSelectedItem) {
  718.       showDownload(aSelectedItem);
  719.     },
  720.     cmd_copyLocation: function(aSelectedItem) {
  721.       copySourceLocation(aSelectedItem);
  722.     },
  723.   }
  724. };
  725.  
  726. /**
  727.  * Helper function to do commands.
  728.  *
  729.  * @param aCmd
  730.  *        The command to be performed.
  731.  * @param aItem
  732.  *        The richlistitem that represents the download that will have the
  733.  *        command performed on it. If this is null, the command is performed on
  734.  *        all downloads. If the item passed in is not a richlistitem that
  735.  *        represents a download, it will walk up the parent nodes until it finds
  736.  *        a DOM node that is.
  737.  */
  738. function performCommand(aCmd, aItem)
  739. {
  740.   let elm = aItem;
  741.   if (!elm) {
  742.     // If we don't have a desired download item, do the command for all
  743.     // selected items. Initialize the callback to null so commands know to add
  744.     // a callback if they want. We will call the callback with empty arguments
  745.     // after performing the command on each selected download item.
  746.     gPerformAllCallback = null;
  747.  
  748.     // Convert the nodelist into an array to keep a copy of the download items
  749.     let items = [];
  750.     for (let i = gDownloadsView.selectedItems.length; --i >= 0; )
  751.       items.unshift(gDownloadsView.selectedItems[i]);
  752.  
  753.     // Do the command for each download item
  754.     for each (let item in items)
  755.       performCommand(aCmd, item);
  756.  
  757.     // Call the callback with no arguments and reset because we're done
  758.     if (typeof gPerformAllCallback == "function")
  759.       gPerformAllCallback();
  760.     gPerformAllCallback = undefined;
  761.  
  762.     return;
  763.   } else {
  764.     while (elm.nodeName != "richlistitem" ||
  765.            elm.getAttribute("type") != "download")
  766.       elm = elm.parentNode;
  767.   }
  768.  
  769.   gDownloadViewController.doCommand(aCmd, elm);
  770. }
  771.  
  772. function setSearchboxFocus()
  773. {
  774.   gSearchBox.focus();
  775.   gSearchBox.select();
  776. }
  777.  
  778. function openExternal(aFile)
  779. {
  780.   var uri = Cc["@mozilla.org/network/io-service;1"].
  781.             getService(Ci.nsIIOService).newFileURI(aFile);
  782.  
  783.   var protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
  784.                     getService(Ci.nsIExternalProtocolService);
  785.   protocolSvc.loadUrl(uri);
  786.  
  787.   return;
  788. }
  789.  
  790. ////////////////////////////////////////////////////////////////////////////////
  791. //// Utility Functions
  792.  
  793. /**
  794.  * Create a download richlistitem with the provided attributes. Some attributes
  795.  * are *required* while optional ones will only be set on the item if provided.
  796.  *
  797.  * @param aAttrs
  798.  *        An object that must have the following properties: dlid, file,
  799.  *        target, uri, state, progress, startTime, endTime, currBytes,
  800.  *        maxBytes; optional properties: referrer
  801.  * @return An initialized download richlistitem
  802.  */
  803. function createDownloadItem(aAttrs)
  804. {
  805.   let dl = document.createElement("richlistitem");
  806.  
  807.   // Copy the attributes from the argument into the item
  808.   for (let attr in aAttrs)
  809.     dl.setAttribute(attr, aAttrs[attr]);
  810.  
  811.   // Initialize other attributes
  812.   dl.setAttribute("type", "download");
  813.   dl.setAttribute("id", "dl" + aAttrs.dlid);
  814.   dl.setAttribute("image", "moz-icon://" + aAttrs.file + "?size=32");
  815.   dl.setAttribute("lastSeconds", Infinity);
  816.  
  817.   // Initialize more complex attributes
  818.   updateTime(dl);
  819.   updateStatus(dl);
  820.  
  821.   try {
  822.     let file = getLocalFileFromNativePathOrUrl(aAttrs.file);
  823.     dl.setAttribute("path", file.nativePath || file.path);
  824.     return dl;
  825.   } catch (e) {
  826.     // aFile might not be a file: url or a valid native path
  827.     // see bug #392386 for details
  828.   }
  829.   return null;
  830. }
  831.  
  832. /**
  833.  * Updates the disabled state of the buttons of a downlaod.
  834.  *
  835.  * @param aItem
  836.  *        The richlistitem representing the download.
  837.  */
  838. function updateButtons(aItem)
  839. {
  840.   let buttons = aItem.buttons;
  841.  
  842.   for (let i = 0; i < buttons.length; ++i) {
  843.     let cmd = buttons[i].getAttribute("cmd");
  844.     let enabled = gDownloadViewController.isCommandEnabled(cmd, aItem);
  845.     buttons[i].disabled = !enabled;
  846.  
  847.     if ("cmd_pause" == cmd && !enabled) {
  848.       // We need to add the tooltip indicating that the download cannot be
  849.       // paused now.
  850.       buttons[i].setAttribute("tooltiptext", gStr.cannotPause);
  851.     }
  852.   }
  853. }
  854.  
  855. /**
  856.  * Updates the status for a download item depending on its state
  857.  *
  858.  * @param aItem
  859.  *        The richlistitem that has various download attributes.
  860.  * @param aDownload
  861.  *        The nsDownload from the backend. This is an optional parameter, but
  862.  *        is useful for certain states such as DOWNLOADING.
  863.  */
  864. function updateStatus(aItem, aDownload) {
  865.   let status = "";
  866.   let statusTip = "";
  867.  
  868.   let state = Number(aItem.getAttribute("state"));
  869.   switch (state) {
  870.     case nsIDM.DOWNLOAD_PAUSED:
  871.     {
  872.       let currBytes = Number(aItem.getAttribute("currBytes"));
  873.       let maxBytes = Number(aItem.getAttribute("maxBytes"));
  874.  
  875.       let transfer = DownloadUtils.getTransferTotal(currBytes, maxBytes);
  876.       status = replaceInsert(gStr.paused, 1, transfer);
  877.  
  878.       break;
  879.     }
  880.     case nsIDM.DOWNLOAD_DOWNLOADING:
  881.     {
  882.       let currBytes = Number(aItem.getAttribute("currBytes"));
  883.       let maxBytes = Number(aItem.getAttribute("maxBytes"));
  884.       // If we don't have an active download, assume 0 bytes/sec
  885.       let speed = aDownload ? aDownload.speed : 0;
  886.       let lastSec = Number(aItem.getAttribute("lastSeconds"));
  887.  
  888.       let newLast;
  889.       [status, newLast] =
  890.         DownloadUtils.getDownloadStatus(currBytes, maxBytes, speed, lastSec);
  891.  
  892.       // Update lastSeconds to be the new value
  893.       aItem.setAttribute("lastSeconds", newLast);
  894.  
  895.       break;
  896.     }
  897.     case nsIDM.DOWNLOAD_FINISHED:
  898.     case nsIDM.DOWNLOAD_FAILED:
  899.     case nsIDM.DOWNLOAD_CANCELED:
  900.     case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
  901.     case nsIDM.DOWNLOAD_BLOCKED_POLICY:
  902.     case nsIDM.DOWNLOAD_DIRTY:
  903.     {
  904.       let (stateSize = {}) {
  905.         stateSize[nsIDM.DOWNLOAD_FINISHED] = function() {
  906.           // Display the file size, but show "Unknown" for negative sizes
  907.           let fileSize = Number(aItem.getAttribute("maxBytes"));
  908.           let sizeText = gStr.doneSizeUnknown;
  909.           if (fileSize >= 0) {
  910.             let [size, unit] = DownloadUtils.convertByteUnits(fileSize);
  911.             sizeText = replaceInsert(gStr.doneSize, 1, size);
  912.             sizeText = replaceInsert(sizeText, 2, unit);
  913.           }
  914.           return sizeText;
  915.         };
  916.         stateSize[nsIDM.DOWNLOAD_FAILED] = function() gStr.stateFailed;
  917.         stateSize[nsIDM.DOWNLOAD_CANCELED] = function() gStr.stateCanceled;
  918.         stateSize[nsIDM.DOWNLOAD_BLOCKED_PARENTAL] = function() gStr.stateBlockedParentalControls;
  919.         stateSize[nsIDM.DOWNLOAD_BLOCKED_POLICY] = function() gStr.stateBlockedPolicy;
  920.         stateSize[nsIDM.DOWNLOAD_DIRTY] = function() gStr.stateDirty;
  921.  
  922.         // Insert 1 is the download size or download state
  923.         status = replaceInsert(gStr.doneStatus, 1, stateSize[state]());
  924.       }
  925.  
  926.       let [displayHost, fullHost] =
  927.         DownloadUtils.getURIHost(getReferrerOrSource(aItem));
  928.       // Insert 2 is the eTLD + 1 or other variations of the host
  929.       status = replaceInsert(status, 2, displayHost);
  930.       // Set the tooltip to be the full host
  931.       statusTip = fullHost;
  932.  
  933.       break;
  934.     }
  935.   }
  936.  
  937.   aItem.setAttribute("status", status);
  938.   aItem.setAttribute("statusTip", statusTip != "" ? statusTip : status);
  939. }
  940.  
  941. /**
  942.  * Updates the time that gets shown for completed download items
  943.  *
  944.  * @param aItem
  945.  *        The richlistitem representing a download in the UI
  946.  */
  947. function updateTime(aItem)
  948. {
  949.   // Don't bother updating for things that aren't finished
  950.   if (aItem.inProgress)
  951.     return;
  952.  
  953.   let dts = Cc["@mozilla.org/intl/scriptabledateformat;1"].
  954.             getService(Ci.nsIScriptableDateFormat);
  955.  
  956.   // Figure out when today begins
  957.   let now = new Date();
  958.   let today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  959.  
  960.   // Get the end time to display
  961.   let end = new Date(parseInt(aItem.getAttribute("endTime")));
  962.  
  963.   // Figure out if the end time is from today, yesterday, this week, etc.
  964.   let dateTime;
  965.   if (end >= today) {
  966.     // Download finished after today started, show the time
  967.     dateTime = dts.FormatTime("", dts.timeFormatNoSeconds,
  968.                               end.getHours(), end.getMinutes(), 0);
  969.   } else if (today - end < (24 * 60 * 60 * 1000)) {
  970.     // Download finished after yesterday started, show yesterday
  971.     dateTime = gStr.yesterday;
  972.   } else if (today - end < (6 * 24 * 60 * 60 * 1000)) {
  973.     // Download finished after last week started, show day of week
  974.     dateTime = end.toLocaleFormat("%A");
  975.   } else {
  976.     // Download must have been from some time ago.. show month/day
  977.     let month = end.toLocaleFormat("%B");
  978.     // Remove leading 0 by converting the date string to a number
  979.     let date = Number(end.toLocaleFormat("%d"));
  980.     dateTime = replaceInsert(gStr.monthDate, 1, month);
  981.     dateTime = replaceInsert(dateTime, 2, date);
  982.   }
  983.  
  984.   aItem.setAttribute("dateTime", dateTime);
  985.  
  986.   // Set the tooltip to be the full date and time
  987.   let dateTimeTip = dts.FormatDateTime("",
  988.                                        dts.dateFormatLong,
  989.                                        dts.timeFormatNoSeconds,
  990.                                        end.getFullYear(),
  991.                                        end.getMonth() + 1,
  992.                                        end.getDate(),
  993.                                        end.getHours(),
  994.                                        end.getMinutes(),
  995.                                        0); 
  996.  
  997.   aItem.setAttribute("dateTimeTip", dateTimeTip);
  998. }
  999.  
  1000. /**
  1001.  * Helper function to replace a placeholder string with a real string
  1002.  *
  1003.  * @param aText
  1004.  *        Source text containing placeholder (e.g., #1)
  1005.  * @param aIndex
  1006.  *        Index number of placeholder to replace
  1007.  * @param aValue
  1008.  *        New string to put in place of placeholder
  1009.  * @return The string with placeholder replaced with the new string
  1010.  */
  1011. function replaceInsert(aText, aIndex, aValue)
  1012. {
  1013.   return aText.replace("#" + aIndex, aValue);
  1014. }
  1015.  
  1016. /**
  1017.  * Perform the default action for the currently selected download item
  1018.  */
  1019. function doDefaultForSelected()
  1020. {
  1021.   // Make sure we have something selected
  1022.   let item = gDownloadsView.selectedItem;
  1023.   if (!item)
  1024.     return;
  1025.  
  1026.   // Get the default action (first item in the menu)
  1027.   let state = Number(item.getAttribute("state"));
  1028.   let menuitem = document.getElementById(gContextMenus[state][0]);
  1029.  
  1030.   // Try to do the action if the command is enabled
  1031.   gDownloadViewController.doCommand(menuitem.getAttribute("cmd"), item);
  1032. }
  1033.  
  1034. function removeFromView(aDownload)
  1035. {
  1036.   // Make sure we have an item to remove
  1037.   if (!aDownload) return;
  1038.  
  1039.   let index = gDownloadsView.selectedIndex;
  1040.   gDownloadsView.removeChild(aDownload);
  1041.   gDownloadsView.selectedIndex = Math.min(index, gDownloadsView.itemCount - 1);
  1042.  
  1043.   // Color everything after from the newly selected item
  1044.   stripeifyList(gDownloadsView.selectedItem);
  1045.  
  1046.   // We might have removed the last item, so update the clear list button
  1047.   updateClearListButton();
  1048. }
  1049.  
  1050. function getReferrerOrSource(aDownload)
  1051. {
  1052.   // Give the referrer if we have it set
  1053.   if (aDownload.hasAttribute("referrer"))
  1054.     return aDownload.getAttribute("referrer");
  1055.  
  1056.   // Otherwise, provide the source
  1057.   return aDownload.getAttribute("uri");
  1058. }
  1059.  
  1060. /**
  1061.  * Initiate building the download list to have the active downloads followed by
  1062.  * completed ones filtered by the search term if necessary.
  1063.  *
  1064.  * @param aForceBuild
  1065.  *        Force the list to be built even if the search terms don't change
  1066.  */
  1067. function buildDownloadList(aForceBuild)
  1068. {
  1069.   // Stringify the previous search
  1070.   let prevSearch = gSearchTerms.join(" ");
  1071.  
  1072.   // Array of space-separated lower-case search terms
  1073.   gSearchTerms = gSearchBox.value.replace(/^\s+|\s+$/g, "").
  1074.                  toLowerCase().split(/\s+/);
  1075.  
  1076.   // Unless forced, don't rebuild the download list if the search didn't change
  1077.   if (!aForceBuild && gSearchTerms.join(" ") == prevSearch)
  1078.     return;
  1079.  
  1080.   // Clear out values before using them
  1081.   clearTimeout(gBuilder);
  1082.   gStmt.reset();
  1083.  
  1084.   // Clear the list before adding items by replacing with a shallow copy
  1085.   let (empty = gDownloadsView.cloneNode(false)) {
  1086.     gDownloadsView.parentNode.replaceChild(empty, gDownloadsView);
  1087.     gDownloadsView = empty;
  1088.   }
  1089.  
  1090.   try {
  1091.     gStmt.bindInt32Parameter(0, nsIDM.DOWNLOAD_NOTSTARTED);
  1092.     gStmt.bindInt32Parameter(1, nsIDM.DOWNLOAD_DOWNLOADING);
  1093.     gStmt.bindInt32Parameter(2, nsIDM.DOWNLOAD_PAUSED);
  1094.     gStmt.bindInt32Parameter(3, nsIDM.DOWNLOAD_QUEUED);
  1095.     gStmt.bindInt32Parameter(4, nsIDM.DOWNLOAD_SCANNING);
  1096.   } catch (e) {
  1097.     // Something must have gone wrong when binding, so clear and quit
  1098.     gStmt.reset();
  1099.     return;
  1100.   }
  1101.  
  1102.   // Take a quick break before we actually start building the list
  1103.   gBuilder = setTimeout(function() {
  1104.     // Start building the list and select the first item
  1105.     stepListBuilder(1);
  1106.     gDownloadsView.selectedIndex = 0;
  1107.  
  1108.     // We just tried to add a single item, so we probably need to enable
  1109.     updateClearListButton();
  1110.   }, 0);
  1111. }
  1112.  
  1113. /**
  1114.  * Incrementally build the download list by adding at most the requested number
  1115.  * of items if there are items to add. After doing that, it will schedule
  1116.  * another chunk of items specified by gListBuildDelay and gListBuildChunk.
  1117.  *
  1118.  * @param aNumItems
  1119.  *        Number of items to add to the list before taking a break
  1120.  */
  1121. function stepListBuilder(aNumItems) {
  1122.   try {
  1123.     // If we're done adding all items, we can quit
  1124.     if (!gStmt.executeStep()) {
  1125.       // Send a notification that we finished, but wait for clear list to update
  1126.       updateClearListButton();
  1127.       setTimeout(function() Cc["@mozilla.org/observer-service;1"].
  1128.         getService(Ci.nsIObserverService).
  1129.         notifyObservers(window, "download-manager-ui-done", null), 0);
  1130.  
  1131.       return;
  1132.     }
  1133.  
  1134.     // Try to get the attribute values from the statement
  1135.     let attrs = {
  1136.       dlid: gStmt.getInt64(0),
  1137.       file: gStmt.getString(1),
  1138.       target: gStmt.getString(2),
  1139.       uri: gStmt.getString(3),
  1140.       state: gStmt.getInt32(4),
  1141.       startTime: Math.round(gStmt.getInt64(5) / 1000),
  1142.       endTime: Math.round(gStmt.getInt64(6) / 1000),
  1143.       currBytes: gStmt.getInt64(8),
  1144.       maxBytes: gStmt.getInt64(9)
  1145.     };
  1146.  
  1147.     // Only add the referrer if it's not null
  1148.     let (referrer = gStmt.getString(7)) {
  1149.       if (referrer)
  1150.         attrs.referrer = referrer;
  1151.     }
  1152.  
  1153.     // If the download is active, grab the real progress, otherwise default 100
  1154.     let isActive = gStmt.getInt32(10);
  1155.     attrs.progress = isActive ? gDownloadManager.getDownload(attrs.dlid).
  1156.       percentComplete : 100;
  1157.  
  1158.     // Make the item and add it to the end if it's active or matches the search
  1159.     let item = createDownloadItem(attrs);
  1160.     if (item && (isActive || downloadMatchesSearch(item))) {
  1161.       // Add item to the end and color just that one item
  1162.       gDownloadsView.appendChild(item);
  1163.       stripeifyList(item);
  1164.     
  1165.       // Because of the joys of XBL, we can't update the buttons until the
  1166.       // download object is in the document.
  1167.       updateButtons(item);
  1168.     } else {
  1169.       // We didn't add an item, so bump up the number of items to process, but
  1170.       // not a whole number so that we eventually do pause for a chunk break
  1171.       aNumItems += .9;
  1172.     }
  1173.   } catch (e) {
  1174.     // Something went wrong when stepping or getting values, so clear and quit
  1175.     gStmt.reset();
  1176.     return;
  1177.   }
  1178.  
  1179.   // Add another item to the list if we should; otherwise, let the UI update
  1180.   // and continue later
  1181.   if (aNumItems > 1) {
  1182.     stepListBuilder(aNumItems - 1);
  1183.   } else {
  1184.     // Use a shorter delay for earlier downloads to display them faster
  1185.     let delay = Math.min(gDownloadsView.itemCount * 10, gListBuildDelay);
  1186.     gBuilder = setTimeout(stepListBuilder, delay, gListBuildChunk);
  1187.   }
  1188. }
  1189.  
  1190. /**
  1191.  * Add a download to the front of the download list
  1192.  *
  1193.  * @param aDownload
  1194.  *        The nsIDownload to make into a richlistitem
  1195.  */
  1196. function prependList(aDownload)
  1197. {
  1198.   let attrs = {
  1199.     dlid: aDownload.id,
  1200.     file: aDownload.target.spec,
  1201.     target: aDownload.displayName,
  1202.     uri: aDownload.source.spec,
  1203.     state: aDownload.state,
  1204.     progress: aDownload.percentComplete,
  1205.     startTime: Math.round(aDownload.startTime / 1000),
  1206.     endTime: Date.now(),
  1207.     currBytes: aDownload.amountTransferred,
  1208.     maxBytes: aDownload.size
  1209.   };
  1210.  
  1211.   // Make the item and add it to the beginning
  1212.   let item = createDownloadItem(attrs);
  1213.   if (item) {
  1214.     // Add item to the beginning and color the whole list
  1215.     gDownloadsView.insertBefore(item, gDownloadsView.firstChild);
  1216.     stripeifyList(item);
  1217.     
  1218.     // Because of the joys of XBL, we can't update the buttons until the
  1219.     // download object is in the document.
  1220.     updateButtons(item);
  1221.  
  1222.     // We might have added an item to an empty list, so update button
  1223.     updateClearListButton();
  1224.   }
  1225. }
  1226.  
  1227. /**
  1228.  * Check if the download matches the current search term based on the texts
  1229.  * shown to the user. All search terms are checked to see if each matches any
  1230.  * of the displayed texts.
  1231.  *
  1232.  * @param aItem
  1233.  *        Download richlistitem to check if it matches the current search
  1234.  * @return Boolean true if it matches the search; false otherwise
  1235.  */
  1236. function downloadMatchesSearch(aItem)
  1237. {
  1238.   // Search through the download attributes that are shown to the user and
  1239.   // make it into one big string for easy combined searching
  1240.   let combinedSearch = "";
  1241.   for each (let attr in gSearchAttributes)
  1242.     combinedSearch += aItem.getAttribute(attr).toLowerCase() + " ";
  1243.  
  1244.   // Make sure each of the terms are found
  1245.   for each (let term in gSearchTerms)
  1246.     if (combinedSearch.search(term) == -1)
  1247.       return false;
  1248.  
  1249.   return true;
  1250. }
  1251.  
  1252. /**
  1253.  * Stripeify the download list by setting or clearing the "alternate" attribute
  1254.  * on items starting from a particular item and continuing to the end.
  1255.  *
  1256.  * @param aItem
  1257.  *        Download rishlist item to start stripeifying
  1258.  */
  1259. function stripeifyList(aItem)
  1260. {
  1261.   let alt = "alternate";
  1262.   // Set the item to be opposite of the other
  1263.   let flipFrom = function(aOther) aOther && aOther.hasAttribute(alt) ?
  1264.     aItem.removeAttribute(alt) : aItem.setAttribute(alt, "true");
  1265.  
  1266.   // Keep coloring items as the opposite of its previous until no more
  1267.   while (aItem) {
  1268.     flipFrom(aItem.previousSibling);
  1269.     aItem = aItem.nextSibling;
  1270.   }
  1271. }
  1272.  
  1273. // we should be using real URLs all the time, but until
  1274. // bug 239948 is fully fixed, this will do...
  1275. //
  1276. // note, this will thrown an exception if the native path
  1277. // is not valid (for example a native Windows path on a Mac)
  1278. // see bug #392386 for details
  1279. function getLocalFileFromNativePathOrUrl(aPathOrUrl)
  1280. {
  1281.   if (aPathOrUrl.substring(0,7) == "file://") {
  1282.     // if this is a URL, get the file from that
  1283.     let ioSvc = Cc["@mozilla.org/network/io-service;1"].
  1284.                 getService(Ci.nsIIOService);
  1285.  
  1286.     // XXX it's possible that using a null char-set here is bad
  1287.     const fileUrl = ioSvc.newURI(aPathOrUrl, null, null).
  1288.                     QueryInterface(Ci.nsIFileURL);
  1289.     return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
  1290.   } else {
  1291.     // if it's a pathname, create the nsILocalFile directly
  1292.     var f = new nsLocalFile(aPathOrUrl);
  1293.  
  1294.     return f;
  1295.   }
  1296. }
  1297.  
  1298. /**
  1299.  * Update the disabled state of the clear list button based on whether or not
  1300.  * there are items in the list that can potentially be removed.
  1301.  */
  1302. function updateClearListButton()
  1303. {
  1304.   let button = document.getElementById("clearListButton");
  1305.   // The button is enabled if we have items in the list and we can clean up
  1306.   button.disabled = !(gDownloadsView.itemCount && gDownloadManager.canCleanUp);
  1307. }
  1308.