mobile/android/chrome/content/aboutDownloads.js

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
     7 Cu.import("resource://gre/modules/Services.jsm");
     8 Cu.import("resource://gre/modules/DownloadUtils.jsm");
     9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    10 Cu.import("resource://gre/modules/PluralForm.jsm");
    11 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
    13 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
    15 let gStrings = Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties");
    17 let downloadTemplate =
    18 "<li downloadGUID='{guid}' class='list-item' role='button' state='{state}' contextmenu='downloadmenu'>" +
    19   "<img class='icon' src='{icon}'/>" +
    20   "<div class='details'>" +
    21      "<div class='row'>" +
    22        // This is a hack so that we can crop this label in its center
    23        "<xul:label class='title' crop='center' value='{target}'/>" +
    24        "<div class='date'>{date}</div>" +
    25      "</div>" +
    26      "<div class='size'>{size}</div>" +
    27      "<div class='domain'>{domain}</div>" +
    28      "<div class='displayState'>{displayState}</div>" +
    29   "</div>" +
    30 "</li>";
    32 XPCOMUtils.defineLazyGetter(window, "gChromeWin", function ()
    33   window.QueryInterface(Ci.nsIInterfaceRequestor)
    34     .getInterface(Ci.nsIWebNavigation)
    35     .QueryInterface(Ci.nsIDocShellTreeItem)
    36     .rootTreeItem
    37     .QueryInterface(Ci.nsIInterfaceRequestor)
    38     .getInterface(Ci.nsIDOMWindow)
    39     .QueryInterface(Ci.nsIDOMChromeWindow));
    42 var ContextMenus = {
    43   target: null,
    45   init: function() {
    46     document.addEventListener("contextmenu", this, false);
    47     document.getElementById("contextmenu-open").addEventListener("click", this.open.bind(this), false);
    48     document.getElementById("contextmenu-retry").addEventListener("click", this.retry.bind(this), false);
    49     document.getElementById("contextmenu-remove").addEventListener("click", this.remove.bind(this), false);
    50     document.getElementById("contextmenu-pause").addEventListener("click", this.pause.bind(this), false);
    51     document.getElementById("contextmenu-resume").addEventListener("click", this.resume.bind(this), false);
    52     document.getElementById("contextmenu-cancel").addEventListener("click", this.cancel.bind(this), false);
    53     document.getElementById("contextmenu-removeall").addEventListener("click", this.removeAll.bind(this), false);
    54     this.items = [
    55       { name: "open", states: [Downloads._dlmgr.DOWNLOAD_FINISHED] },
    56       { name: "retry", states: [Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
    57       { name: "remove", states: [Downloads._dlmgr.DOWNLOAD_FINISHED,Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
    58       { name: "removeall", states: [Downloads._dlmgr.DOWNLOAD_FINISHED,Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] },
    59       { name: "pause", states: [Downloads._dlmgr.DOWNLOAD_DOWNLOADING] },
    60       { name: "resume", states: [Downloads._dlmgr.DOWNLOAD_PAUSED] },
    61       { name: "cancel", states: [Downloads._dlmgr.DOWNLOAD_DOWNLOADING, Downloads._dlmgr.DOWNLOAD_NOTSTARTED, Downloads._dlmgr.DOWNLOAD_QUEUED, Downloads._dlmgr.DOWNLOAD_PAUSED] },
    62     ];
    63   },
    65   handleEvent: function(event) {
    66     // store the target of context menu events so that we know which app to act on
    67     this.target = event.target;
    68     while (!this.target.hasAttribute("contextmenu")) {
    69       this.target = this.target.parentNode;
    70     }
    71     if (!this.target)
    72       return;
    74     let state = parseInt(this.target.getAttribute("state"));
    75     for (let i = 0; i < this.items.length; i++) {
    76       var item = this.items[i];
    77       let enabled = (item.states.indexOf(state) > -1);
    78       if (enabled)
    79         document.getElementById("contextmenu-" + item.name).removeAttribute("hidden");
    80       else
    81         document.getElementById("contextmenu-" + item.name).setAttribute("hidden", "true");
    82     }
    83   },
    85   // Open shown only for downloads that completed successfully
    86   open: function(event) {
    87     Downloads.openDownload(this.target);
    88     this.target = null;
    89   },
    91   // Retry shown when its failed, canceled, blocked(covered in failed, see _getState())
    92   retry: function (event) {
    93     Downloads.retryDownload(this.target);
    94     this.target = null;
    95   },
    97   // Remove shown when its canceled, finished, failed(failed includes blocked and dirty, see _getState())
    98   remove: function (event) {
    99     Downloads.removeDownload(this.target);
   100     this.target = null;
   101   },
   103   // Pause shown when item is currently downloading
   104   pause: function (event) {
   105     Downloads.pauseDownload(this.target);
   106     this.target = null;
   107   },
   109   // Resume shown for paused items only
   110   resume: function (event) {
   111     Downloads.resumeDownload(this.target);
   112     this.target = null;
   113   },
   115   // Cancel shown when its downloading, notstarted, queued or paused
   116   cancel: function (event) {
   117     Downloads.cancelDownload(this.target);
   118     this.target = null;
   119   },
   121   removeAll: function(event) {
   122     Downloads.removeAll();
   123     this.target = null;
   124   }
   125 }
   128 let Downloads = {
   129   init: function dl_init() {
   130     function onClick(evt) {
   131       let target = evt.target;
   132       while (target.nodeName != "li") {
   133         target = target.parentNode;
   134         if (!target)
   135           return;
   136       }
   138       Downloads.openDownload(target);
   139     }
   141     this._normalList = document.getElementById("normal-downloads-list");
   142     this._privateList = document.getElementById("private-downloads-list");
   144     this._normalList.addEventListener("click", onClick, false);
   145     this._privateList.addEventListener("click", onClick, false);
   147     this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
   148     this._dlmgr.addPrivacyAwareListener(this);
   150     Services.obs.addObserver(this, "last-pb-context-exited", false);
   151     Services.obs.addObserver(this, "download-manager-remove-download-guid", false);
   153     // If we have private downloads, show them all immediately. If we were to
   154     // add them asynchronously, there's a small chance we could get a
   155     // "last-pb-context-exited" notification before downloads are added to the
   156     // list, meaning we'd show private downloads without any private tabs open.
   157     let privateEntries = this.getDownloads({ isPrivate: true });
   158     this._stepAddEntries(privateEntries, this._privateList, privateEntries.length);
   160     // Add non-private downloads
   161     let normalEntries = this.getDownloads({ isPrivate: false });    
   162     this._stepAddEntries(normalEntries, this._normalList, 1, this._scrollToSelectedDownload.bind(this));    
   163     ContextMenus.init();    
   164   },
   166   uninit: function dl_uninit() {
   167     let contextmenus = gChromeWin.NativeWindow.contextmenus;
   168     contextmenus.remove(this.openMenuItem);
   169     contextmenus.remove(this.removeMenuItem);
   170     contextmenus.remove(this.pauseMenuItem);
   171     contextmenus.remove(this.resumeMenuItem);
   172     contextmenus.remove(this.retryMenuItem);
   173     contextmenus.remove(this.cancelMenuItem);
   174     contextmenus.remove(this.deleteAllMenuItem);
   176     this._dlmgr.removeListener(this);
   177     Services.obs.removeObserver(this, "last-pb-context-exited");
   178     Services.obs.removeObserver(this, "download-manager-remove-download-guid");
   179   },
   181   onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress,
   182                              aCurTotalProgress, aMaxTotalProgress, aDownload) { },
   183   onDownloadStateChange: function(aState, aDownload) {
   184     switch (aDownload.state) {
   185       case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
   186       case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
   187       case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
   188       case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
   189       case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
   190         // For all "completed" states, move them after active downloads
   191         this._moveDownloadAfterActive(this._getElementForDownload(aDownload.guid));
   193       // Fall-through the rest
   194       case Ci.nsIDownloadManager.DOWNLOAD_SCANNING:
   195       case Ci.nsIDownloadManager.DOWNLOAD_QUEUED:
   196       case Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING:
   197         let item = this._getElementForDownload(aDownload.guid);
   198         if (item)
   199           this._updateDownloadRow(item, aDownload);
   200         else
   201           this._insertDownloadRow(aDownload);
   202         break;
   203     }
   204   },
   205   onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
   206   onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { },
   208   observe: function (aSubject, aTopic, aData) {
   209     switch (aTopic) {
   210       case "last-pb-context-exited":
   211         this._privateList.innerHTML = "";
   212         break;
   213       case "download-manager-remove-download-guid": {
   214         let guid = aSubject.QueryInterface(Ci.nsISupportsCString).data;
   215         this._removeItem(this._getElementForDownload(guid));
   216         break;
   217       }
   218     }
   219   },
   221   _moveDownloadAfterActive: function dl_moveDownloadAfterActive(aItem) {
   222     // Move downloads that just reached a "completed" state below any active
   223     try {
   224       // Iterate down until we find a non-active download
   225       let next = aItem.nextElementSibling;
   226       while (next && this._inProgress(next.getAttribute("state")))
   227         next = next.nextElementSibling;
   228       // Move the item
   229       aItem.parentNode.insertBefore(aItem, next);
   230     } catch (ex) {
   231       this.logError("_moveDownloadAfterActive() " + ex);
   232     }
   233   },
   235   _inProgress: function dl_inProgress(aState) {
   236     return [
   237       this._dlmgr.DOWNLOAD_NOTSTARTED,
   238       this._dlmgr.DOWNLOAD_QUEUED,
   239       this._dlmgr.DOWNLOAD_DOWNLOADING,
   240       this._dlmgr.DOWNLOAD_PAUSED,
   241       this._dlmgr.DOWNLOAD_SCANNING,
   242     ].indexOf(parseInt(aState)) != -1;
   243   },
   245   _insertDownloadRow: function dl_insertDownloadRow(aDownload) {
   246     let updatedState = this._getState(aDownload.state);
   247     let item = this._createItem(downloadTemplate, {
   248       guid: aDownload.guid,
   249       target: aDownload.displayName,
   250       icon: "moz-icon://" + aDownload.displayName + "?size=64",
   251       date: DownloadUtils.getReadableDates(new Date())[0],
   252       domain: DownloadUtils.getURIHost(aDownload.source.spec)[0],
   253       size: this._getDownloadSize(aDownload.size),
   254       displayState: this._getStateString(updatedState),
   255       state: updatedState
   256     });
   257     list = aDownload.isPrivate ? this._privateList : this._normalList;
   258     list.insertAdjacentHTML("afterbegin", item);
   259   },
   261   _getDownloadSize: function dl_getDownloadSize(aSize) {
   262     if (aSize > 0) {
   263       let displaySize = DownloadUtils.convertByteUnits(aSize);
   264       return displaySize.join(""); // [0] is size, [1] is units
   265     }
   266     return gStrings.GetStringFromName("downloadState.unknownSize");
   267   },
   269   // Not all states are displayed as-is on mobile, some are translated to a generic state
   270   _getState: function dl_getState(aState) {
   271     let str;
   272     switch (aState) {
   273       // Downloading and Scanning states show up as "Downloading"
   274       case this._dlmgr.DOWNLOAD_DOWNLOADING:
   275       case this._dlmgr.DOWNLOAD_SCANNING:
   276         str = this._dlmgr.DOWNLOAD_DOWNLOADING;
   277         break;
   279       // Failed, Dirty and Blocked states show up as "Failed"
   280       case this._dlmgr.DOWNLOAD_FAILED:
   281       case this._dlmgr.DOWNLOAD_DIRTY:
   282       case this._dlmgr.DOWNLOAD_BLOCKED_POLICY:
   283       case this._dlmgr.DOWNLOAD_BLOCKED_PARENTAL:
   284         str = this._dlmgr.DOWNLOAD_FAILED;
   285         break;
   287       /* QUEUED and NOTSTARTED are not translated as they
   288          dont fall under a common state but we still need
   289          to display a common "status" on the UI */
   291       default:
   292         str = aState;
   293     }
   294     return str;
   295   },
   297   // Note: This doesn't cover all states as some of the states are translated in _getState()
   298   _getStateString: function dl_getStateString(aState) {
   299     let str;
   300     switch (aState) {
   301       case this._dlmgr.DOWNLOAD_DOWNLOADING:
   302         str = "downloadState.downloading";
   303         break;
   304       case this._dlmgr.DOWNLOAD_CANCELED:
   305         str = "downloadState.canceled";
   306         break;
   307       case this._dlmgr.DOWNLOAD_FAILED:
   308         str = "downloadState.failed";
   309         break;
   310       case this._dlmgr.DOWNLOAD_PAUSED:
   311         str = "downloadState.paused";
   312         break;
   314       // Queued and Notstarted show up as "Starting..."
   315       case this._dlmgr.DOWNLOAD_QUEUED:
   316       case this._dlmgr.DOWNLOAD_NOTSTARTED:
   317         str = "downloadState.starting";
   318         break;
   320       default:
   321         return "";
   322     }
   323     return gStrings.GetStringFromName(str);
   324   },
   326   _updateItem: function dl_updateItem(aItem, aValues) {
   327     for (let i in aValues) {
   328       aItem.querySelector("." + i).textContent = aValues[i];
   329     }
   330   },
   332   _initStatement: function dv__initStatement(aIsPrivate) {
   333     let dbConn = aIsPrivate ? this._dlmgr.privateDBConnection : this._dlmgr.DBConnection;
   334     return dbConn.createStatement(
   335       "SELECT guid, name, source, state, startTime, endTime, referrer, " +
   336              "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " +
   337       "FROM moz_downloads " +
   338       "ORDER BY isActive DESC, endTime DESC, startTime DESC");
   339   },
   341   _createItem: function _createItem(aTemplate, aValues) {
   342     function htmlEscape(s) {
   343       s = s.replace(/&/g, "&amp;");
   344       s = s.replace(/>/g, "&gt;");
   345       s = s.replace(/</g, "&lt;");
   346       s = s.replace(/"/g, "&quot;");
   347       s = s.replace(/'/g, "&apos;");
   348       return s;
   349     }
   351     let t = aTemplate;
   352     for (let key in aValues) {
   353       if (aValues.hasOwnProperty(key)) {
   354         let regEx = new RegExp("{" + key + "}", "g");
   355         let value = htmlEscape(aValues[key].toString());
   356         t = t.replace(regEx, value);
   357       }
   358     }
   359     return t;
   360   },
   362   _getEntry: function dv__getEntry(aStmt) {
   363     try {
   364       if (!aStmt.executeStep()) {
   365         return null;
   366       }
   368       let updatedState = this._getState(aStmt.row.state);
   369       // Try to get the attribute values from the statement
   371       return {
   372         guid: aStmt.row.guid,
   373         target: aStmt.row.name,
   374         icon: "moz-icon://" + aStmt.row.name + "?size=64",
   375         date: DownloadUtils.getReadableDates(new Date(aStmt.row.endTime / 1000))[0],
   376         domain: DownloadUtils.getURIHost(aStmt.row.source)[0],
   377         size: this._getDownloadSize(aStmt.row.maxBytes),
   378         displayState: this._getStateString(updatedState),
   379         state: updatedState
   380       };
   382     } catch (e) {
   383       // Something went wrong when stepping or getting values, so clear and quit
   384       this.logError("_getEntry() " + e);
   385       aStmt.reset();
   386       return null;
   387     }
   388   },
   390   _stepAddEntries: function dv__stepAddEntries(aEntries, aList, aNumItems, aCallback) {
   392     if (aEntries.length == 0){
   393       if (aCallback)
   394         aCallback();
   396       return;
   397     }
   399     let attrs = aEntries.shift();
   400     let item = this._createItem(downloadTemplate, attrs);
   401     aList.insertAdjacentHTML("beforeend", item);
   403     // Add another item to the list if we should; otherwise, let the UI update
   404     // and continue later
   405     if (aNumItems > 1) {
   406       this._stepAddEntries(aEntries, aList, aNumItems - 1, aCallback);
   407     } else {
   408       // Use a shorter delay for earlier downloads to display them faster
   409       let delay = Math.min(aList.itemCount * 10, 300);
   410       setTimeout(function () {
   411         this._stepAddEntries(aEntries, aList, 5, aCallback);
   412       }.bind(this), delay);
   413     }
   414   },
   416   getDownloads: function dl_getDownloads(aParams) {
   417     aParams = aParams || {};
   418     let stmt = this._initStatement(aParams.isPrivate);
   420     stmt.reset();
   421     stmt.bindInt32Parameter(0, Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED);
   422     stmt.bindInt32Parameter(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING);
   423     stmt.bindInt32Parameter(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED);
   424     stmt.bindInt32Parameter(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED);
   425     stmt.bindInt32Parameter(4, Ci.nsIDownloadManager.DOWNLOAD_SCANNING);
   427     let entries = [];
   428     while (entry = this._getEntry(stmt)) {
   429       entries.push(entry);
   430     }
   432     stmt.finalize();
   434     return entries;
   435   },
   437   _getElementForDownload: function dl_getElementForDownload(aKey) {
   438     return document.body.querySelector("li[downloadGUID='" + aKey + "']");
   439   },
   441   _getDownloadForElement: function dl_getDownloadForElement(aElement, aCallback) {
   442     let guid = aElement.getAttribute("downloadGUID");
   443     this._dlmgr.getDownloadByGUID(guid, function(status, download) {
   444       if (!Components.isSuccessCode(status)) {
   445         return;
   446       }
   447       aCallback(download);
   448     });
   449   },
   451   _removeItem: function dl_removeItem(aItem) {
   452     // Make sure we have an item to remove
   453     if (!aItem)
   454       return;
   456     aItem.parentNode.removeChild(aItem);
   457   },
   459   openDownload: function dl_openDownload(aItem) {
   460     this._getDownloadForElement(aItem, function(aDownload) {
   461       if (aDownload.state !== Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
   462         // Do not open unfinished downloads.
   463         return;
   464       }
   465       try {
   466         let f = aDownload.targetFile;
   467         if (f) f.launch();
   468       } catch (ex) {
   469         this.logError("openDownload() " + ex, aDownload);
   470       }
   471     }.bind(this));
   472   },
   474   removeDownload: function dl_removeDownload(aItem) {
   475     this._getDownloadForElement(aItem, function(aDownload) {
   476       if (aDownload.targetFile) {
   477         OS.File.remove(aDownload.targetFile.path).then(null, function onError(reason) {
   478           if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) {
   479             this.logError("removeDownload() " + reason, aDownload);
   480           }
   481         }.bind(this));
   482       }
   484       aDownload.remove();
   485     }.bind(this));
   486   },
   488   removeAll: function dl_removeAll() {
   489     let title = gStrings.GetStringFromName("downloadAction.deleteAll");
   490     let messageForm = gStrings.GetStringFromName("downloadMessage.deleteAll");
   491     let elements = document.body.querySelectorAll("li[state='" + this._dlmgr.DOWNLOAD_FINISHED + "']," +
   492                                                "li[state='" + this._dlmgr.DOWNLOAD_CANCELED + "']," +
   493                                                "li[state='" + this._dlmgr.DOWNLOAD_FAILED + "']");
   494     let message = PluralForm.get(elements.length, messageForm)
   495                             .replace("#1", elements.length);
   496     let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_OK +
   497                 Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL;
   498     let choice = Services.prompt.confirmEx(null, title, message, flags,
   499                                            null, null, null, null, {});
   500     if (choice == 0) {
   501       for (let i = 0; i < elements.length; i++) {
   502         this.removeDownload(elements[i]);
   503       }
   504     }
   505   },
   507   pauseDownload: function dl_pauseDownload(aItem) {
   508     this._getDownloadForElement(aItem, function(aDownload) {
   509       try {
   510         aDownload.pause();
   511         this._updateDownloadRow(aItem, aDownload);
   512       } catch (ex) {
   513         this.logError("Error: pauseDownload() " + ex, aDownload);
   514       }
   515     }.bind(this));
   516   },
   518   resumeDownload: function dl_resumeDownload(aItem) {
   519     this._getDownloadForElement(aItem, function(aDownload) {
   520       try {
   521         aDownload.resume();
   522         this._updateDownloadRow(aItem, aDownload);
   523       } catch (ex) {
   524         this.logError("resumeDownload() " + ex, aDownload);
   525       }
   526     }.bind(this));
   527   },
   529   retryDownload: function dl_retryDownload(aItem) {
   530     this._getDownloadForElement(aItem, function(aDownload) {
   531       try {
   532         this._removeItem(aItem);
   533         aDownload.retry();
   534       } catch (ex) {
   535         this.logError("retryDownload() " + ex, aDownload);
   536       }
   537     }.bind(this));
   538   },
   540   cancelDownload: function dl_cancelDownload(aItem) {
   541     this._getDownloadForElement(aItem, function(aDownload) {
   542       OS.File.remove(aDownload.targetFile.path).then(null, function onError(reason) {
   543         if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) {
   544           this.logError("cancelDownload() " + reason, aDownload);
   545         }
   546       }.bind(this));
   548       aDownload.cancel();
   550       this._updateDownloadRow(aItem, aDownload);
   551     }.bind(this));
   552   },
   554   _updateDownloadRow: function dl_updateDownloadRow(aItem, aDownload) {
   555     try {
   556       let updatedState = this._getState(aDownload.state);
   557       aItem.setAttribute("state", updatedState);
   558       this._updateItem(aItem, {
   559         size: this._getDownloadSize(aDownload.size),
   560         displayState: this._getStateString(updatedState),
   561         date: DownloadUtils.getReadableDates(new Date())[0]
   562       });
   563     } catch (ex) {
   564       this.logError("_updateDownloadRow() " + ex, aDownload);
   565     }
   566   },
   568   /**
   569    * In case a specific downloadId was passed while opening, scrolls the list to 
   570    * the given elemenet
   571    */
   573   _scrollToSelectedDownload : function dl_scrollToSelected() {
   574     let spec = document.location.href;
   575     let pos = spec.indexOf("?");
   576     let query = "";
   577     if (pos >= 0)
   578       query = spec.substring(pos + 1);
   580     // Just assume the query is "id=<id>"
   581     let id = query.substring(3);
   582     if (!id) {
   583       return;
   584     }    
   585     downloadElement = this._getElementForDownload(id);
   586     if (!downloadElement) {
   587       return;
   588     }
   590     downloadElement.scrollIntoView();
   591   },
   593   /**
   594    * Logs the error to the console.
   595    *
   596    * @param aMessage  error message to log
   597    * @param aDownload (optional) if given, and if the download is private, the
   598    *                  log message is suppressed
   599    */
   600   logError: function dl_logError(aMessage, aDownload) {
   601     if (!aDownload || !aDownload.isPrivate) {
   602       console.log("Error: " + aMessage);
   603     }
   604   },
   606   QueryInterface: function (aIID) {
   607     if (!aIID.equals(Ci.nsIDownloadProgressListener) &&
   608         !aIID.equals(Ci.nsISupports))
   609       throw Components.results.NS_ERROR_NO_INTERFACE;
   610     return this;
   611   }
   612 }
   614 document.addEventListener("DOMContentLoaded", Downloads.init.bind(Downloads), true);
   615 window.addEventListener("unload", Downloads.uninit.bind(Downloads), false);

mercurial