675 lines
16 KiB
JavaScript
675 lines
16 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
const EXPORTED_SYMBOLS = ["S4EDownloadService"];
|
|
|
|
const CC = Components.classes;
|
|
const CI = Components.interfaces;
|
|
const CU = Components.utils;
|
|
|
|
CU.import("resource://gre/modules/Services.jsm");
|
|
CU.import("resource://gre/modules/PluralForm.jsm");
|
|
CU.import("resource://gre/modules/DownloadUtils.jsm");
|
|
CU.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
|
CU.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
function S4EDownloadService(window, gBrowser, service, getters)
|
|
{
|
|
this._window = window;
|
|
this._gBrowser = gBrowser;
|
|
this._service = service;
|
|
this._getters = getters;
|
|
|
|
this._handler = new JSTransferHandler(this._window, this);
|
|
}
|
|
|
|
S4EDownloadService.prototype =
|
|
{
|
|
_window: null,
|
|
_gBrowser: null,
|
|
_service: null,
|
|
_getters: null,
|
|
|
|
_handler: null,
|
|
_listening: false,
|
|
|
|
_binding: false,
|
|
_customizing: false,
|
|
|
|
_lastTime: Infinity,
|
|
|
|
_dlActive: false,
|
|
_dlPaused: false,
|
|
_dlFinished: false,
|
|
|
|
_dlCountStr: null,
|
|
_dlTimeStr: null,
|
|
|
|
_dlProgressAvg: 0,
|
|
_dlProgressMax: 0,
|
|
_dlProgressMin: 0,
|
|
_dlProgressType: "active",
|
|
|
|
_dlNotifyTimer: 0,
|
|
_dlNotifyGlowTimer: 0,
|
|
|
|
init: function()
|
|
{
|
|
if(!this._getters.downloadButton)
|
|
{
|
|
this.uninit();
|
|
return;
|
|
}
|
|
|
|
if(this._listening)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this._handler.start();
|
|
this._listening = true;
|
|
|
|
this._lastTime = Infinity;
|
|
|
|
this.updateBinding();
|
|
this.updateStatus();
|
|
},
|
|
|
|
uninit: function()
|
|
{
|
|
if(!this._listening)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this._listening = false;
|
|
this._handler.stop();
|
|
|
|
this.releaseBinding();
|
|
},
|
|
|
|
destroy: function()
|
|
{
|
|
this.uninit();
|
|
this._handler.destroy();
|
|
|
|
["_window", "_gBrowser", "_service", "_getters", "_handler"].forEach(function(prop)
|
|
{
|
|
delete this[prop];
|
|
}, this);
|
|
},
|
|
|
|
updateBinding: function()
|
|
{
|
|
if(!this._listening)
|
|
{
|
|
this.releaseBinding();
|
|
return;
|
|
}
|
|
|
|
switch(this._service.downloadButtonAction)
|
|
{
|
|
case 1: // Default
|
|
this.attachBinding();
|
|
break;
|
|
default:
|
|
this.releaseBinding();
|
|
break;
|
|
}
|
|
},
|
|
|
|
attachBinding: function()
|
|
{
|
|
if(this._binding)
|
|
{
|
|
return;
|
|
}
|
|
|
|
let db = this._window.DownloadsButton;
|
|
|
|
db._getAnchorS4EBackup = db.getAnchor;
|
|
db.getAnchor = this.getAnchor.bind(this);
|
|
|
|
db._releaseAnchorS4EBackup = db.releaseAnchor;
|
|
db.releaseAnchor = function() {};
|
|
|
|
this._binding = true;
|
|
},
|
|
|
|
releaseBinding: function()
|
|
{
|
|
if(!this._binding)
|
|
{
|
|
return;
|
|
}
|
|
|
|
let db = this._window.DownloadsButton;
|
|
|
|
db.getAnchor = db._getAnchorS4EBackup;
|
|
db.releaseAnchor = db._releaseAnchorS4EBackup;
|
|
|
|
this._binding = false;
|
|
},
|
|
|
|
customizing: function(val)
|
|
{
|
|
this._customizing = val;
|
|
},
|
|
|
|
updateStatus: function(lastFinished)
|
|
{
|
|
if(!this._getters.downloadButton)
|
|
{
|
|
this.uninit();
|
|
return;
|
|
}
|
|
|
|
let numActive = 0;
|
|
let numPaused = 0;
|
|
let activeTotalSize = 0;
|
|
let activeTransferred = 0;
|
|
let activeMaxProgress = -Infinity;
|
|
let activeMinProgress = Infinity;
|
|
let pausedTotalSize = 0;
|
|
let pausedTransferred = 0;
|
|
let pausedMaxProgress = -Infinity;
|
|
let pausedMinProgress = Infinity;
|
|
let maxTime = -Infinity;
|
|
|
|
let dls = ((this.isPrivateWindow) ? this._handler.activePrivateEntries() : this._handler.activeEntries());
|
|
for(let dl of dls)
|
|
{
|
|
if(dl.state == CI.nsIDownloadManager.DOWNLOAD_DOWNLOADING)
|
|
{
|
|
numActive++;
|
|
if(dl.size > 0)
|
|
{
|
|
if(dl.speed > 0)
|
|
{
|
|
maxTime = Math.max(maxTime, (dl.size - dl.transferred) / dl.speed);
|
|
}
|
|
|
|
activeTotalSize += dl.size;
|
|
activeTransferred += dl.transferred;
|
|
|
|
let currentProgress = ((dl.transferred * 100) / dl.size);
|
|
activeMaxProgress = Math.max(activeMaxProgress, currentProgress);
|
|
activeMinProgress = Math.min(activeMinProgress, currentProgress);
|
|
}
|
|
}
|
|
else if(dl.state == CI.nsIDownloadManager.DOWNLOAD_PAUSED)
|
|
{
|
|
numPaused++;
|
|
if(dl.size > 0)
|
|
{
|
|
pausedTotalSize += dl.size;
|
|
pausedTransferred += dl.transferred;
|
|
|
|
let currentProgress = ((dl.transferred * 100) / dl.size);
|
|
pausedMaxProgress = Math.max(pausedMaxProgress, currentProgress);
|
|
pausedMinProgress = Math.min(pausedMinProgress, currentProgress);
|
|
}
|
|
}
|
|
}
|
|
|
|
if((numActive + numPaused) == 0)
|
|
{
|
|
this._dlActive = false;
|
|
this._dlFinished = lastFinished;
|
|
this.updateButton();
|
|
this._lastTime = Infinity;
|
|
return;
|
|
}
|
|
|
|
let dlPaused = (numActive == 0);
|
|
let dlStatus = ((dlPaused) ? this._getters.strings.getString("pausedDownloads")
|
|
: this._getters.strings.getString("activeDownloads"));
|
|
let dlCount = ((dlPaused) ? numPaused : numActive);
|
|
let dlTotalSize = ((dlPaused) ? pausedTotalSize : activeTotalSize);
|
|
let dlTransferred = ((dlPaused) ? pausedTransferred : activeTransferred);
|
|
let dlMaxProgress = ((dlPaused) ? pausedMaxProgress : activeMaxProgress);
|
|
let dlMinProgress = ((dlPaused) ? pausedMinProgress : activeMinProgress);
|
|
let dlProgressType = ((dlPaused) ? "paused" : "active");
|
|
|
|
[this._dlTimeStr, this._lastTime] = DownloadUtils.getTimeLeft(maxTime, this._lastTime);
|
|
this._dlCountStr = PluralForm.get(dlCount, dlStatus).replace("#1", dlCount);
|
|
this._dlProgressAvg = ((dlTotalSize == 0) ? 100 : ((dlTransferred * 100) / dlTotalSize));
|
|
this._dlProgressMax = ((dlTotalSize == 0) ? 100 : dlMaxProgress);
|
|
this._dlProgressMin = ((dlTotalSize == 0) ? 100 : dlMinProgress);
|
|
this._dlProgressType = dlProgressType + ((dlTotalSize == 0) ? "-unknown" : "");
|
|
this._dlPaused = dlPaused;
|
|
this._dlActive = true;
|
|
this._dlFinished = false;
|
|
|
|
this.updateButton();
|
|
},
|
|
|
|
updateButton: function()
|
|
{
|
|
let download_button = this._getters.downloadButton;
|
|
let download_tooltip = this._getters.downloadButtonTooltip;
|
|
let download_progress = this._getters.downloadButtonProgress;
|
|
let download_label = this._getters.downloadButtonLabel;
|
|
if(!download_button)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!this._dlActive)
|
|
{
|
|
download_button.collapsed = true;
|
|
download_label.value = download_tooltip.label = this._getters.strings.getString("noDownloads");
|
|
|
|
download_progress.collapsed = true;
|
|
download_progress.value = 0;
|
|
|
|
if(this._dlFinished && this._handler.hasPBAPI && !this.isUIShowing)
|
|
{
|
|
this.callAttention(download_button);
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch(this._service.downloadProgress)
|
|
{
|
|
case 2:
|
|
download_progress.value = this._dlProgressMax;
|
|
break;
|
|
case 3:
|
|
download_progress.value = this._dlProgressMin;
|
|
break;
|
|
default:
|
|
download_progress.value = this._dlProgressAvg;
|
|
break;
|
|
}
|
|
download_progress.setAttribute("pmType", this._dlProgressType);
|
|
download_progress.collapsed = (this._service.downloadProgress == 0);
|
|
|
|
download_label.value = this.buildString(this._service.downloadLabel);
|
|
download_tooltip.label = this.buildString(this._service.downloadTooltip);
|
|
|
|
this.clearAttention(download_button);
|
|
download_button.collapsed = false;
|
|
},
|
|
|
|
callAttention: function(download_button)
|
|
{
|
|
if(this._dlNotifyGlowTimer != 0)
|
|
{
|
|
this._window.clearTimeout(this._dlNotifyGlowTimer);
|
|
this._dlNotifyGlowTimer = 0;
|
|
}
|
|
|
|
download_button.setAttribute("attention", "true");
|
|
|
|
if(this._service.downloadNotifyTimeout)
|
|
{
|
|
this._dlNotifyGlowTimer = this._window.setTimeout(function(self, button)
|
|
{
|
|
self._dlNotifyGlowTimer = 0;
|
|
button.removeAttribute("attention");
|
|
}, this._service.downloadNotifyTimeout, this, download_button);
|
|
}
|
|
},
|
|
|
|
clearAttention: function(download_button)
|
|
{
|
|
if(this._dlNotifyGlowTimer != 0)
|
|
{
|
|
this._window.clearTimeout(this._dlNotifyGlowTimer);
|
|
this._dlNotifyGlowTimer = 0;
|
|
}
|
|
|
|
download_button.removeAttribute("attention");
|
|
},
|
|
|
|
notify: function()
|
|
{
|
|
if(this._dlNotifyTimer == 0 && this._service.downloadNotifyAnimate)
|
|
{
|
|
let download_button_anchor = this._getters.downloadButtonAnchor;
|
|
let download_notify_anchor = this._getters.downloadNotifyAnchor;
|
|
if(download_button_anchor)
|
|
{
|
|
if(!download_notify_anchor.style.transform)
|
|
{
|
|
let bAnchorRect = download_button_anchor.getBoundingClientRect();
|
|
let nAnchorRect = download_notify_anchor.getBoundingClientRect();
|
|
|
|
let translateX = bAnchorRect.left - nAnchorRect.left;
|
|
translateX += .5 * (bAnchorRect.width - nAnchorRect.width);
|
|
|
|
let translateY = bAnchorRect.top - nAnchorRect.top;
|
|
translateY += .5 * (bAnchorRect.height - nAnchorRect.height);
|
|
|
|
download_notify_anchor.style.transform = "translate(" + translateX + "px, " + translateY + "px)";
|
|
}
|
|
|
|
download_notify_anchor.setAttribute("notification", "finish");
|
|
this._dlNotifyTimer = this._window.setTimeout(function(self, anchor)
|
|
{
|
|
self._dlNotifyTimer = 0;
|
|
anchor.removeAttribute("notification");
|
|
anchor.style.transform = "";
|
|
}, 1000, this, download_notify_anchor);
|
|
}
|
|
}
|
|
},
|
|
|
|
clearFinished: function()
|
|
{
|
|
this._dlFinished = false;
|
|
let download_button = this._getters.downloadButton;
|
|
if(download_button)
|
|
{
|
|
this.clearAttention(download_button);
|
|
}
|
|
},
|
|
|
|
getAnchor: function(aCallback)
|
|
{
|
|
if(this._customizing)
|
|
{
|
|
aCallback(null);
|
|
return;
|
|
}
|
|
|
|
aCallback(this._getters.downloadButtonAnchor);
|
|
},
|
|
|
|
openUI: function(aEvent)
|
|
{
|
|
this.clearFinished();
|
|
|
|
switch(this._service.downloadButtonAction)
|
|
{
|
|
case 1: // Firefox Default
|
|
this._handler.openUINative();
|
|
break;
|
|
case 2: // Show Library
|
|
this._window.PlacesCommandHook.showPlacesOrganizer("Downloads");
|
|
break;
|
|
case 3: // Show Tab
|
|
let found = this._gBrowser.browsers.some(function(browser, index)
|
|
{
|
|
if("about:downloads" == browser.currentURI.spec)
|
|
{
|
|
this._gBrowser.selectedTab = this._gBrowser.tabContainer.childNodes[index];
|
|
return true;
|
|
}
|
|
}, this);
|
|
|
|
if(!found)
|
|
{
|
|
this._window.openUILinkIn("about:downloads", "tab");
|
|
}
|
|
break;
|
|
case 4: // External Command
|
|
let command = this._service.downloadButtonActionCommand;
|
|
if(commend)
|
|
{
|
|
this._window.goDoCommand(command);
|
|
}
|
|
break;
|
|
default: // Nothing
|
|
break;
|
|
}
|
|
|
|
aEvent.stopPropagation();
|
|
},
|
|
|
|
get isPrivateWindow()
|
|
{
|
|
return this._handler.hasPBAPI && PrivateBrowsingUtils.isWindowPrivate(this._window);
|
|
},
|
|
|
|
get isUIShowing()
|
|
{
|
|
switch(this._service.downloadButtonAction)
|
|
{
|
|
case 1: // Firefox Default
|
|
return this._handler.isUIShowingNative;
|
|
case 2: // Show Library
|
|
var organizer = Services.wm.getMostRecentWindow("Places:Organizer");
|
|
if(organizer)
|
|
{
|
|
let selectedNode = organizer.PlacesOrganizer._places.selectedNode;
|
|
let downloadsItemId = organizer.PlacesUIUtils.leftPaneQueries["Downloads"];
|
|
return selectedNode && selectedNode.itemId === downloadsItemId;
|
|
}
|
|
return false;
|
|
case 3: // Show tab
|
|
let currentURI = this._gBrowser.currentURI;
|
|
return currentURI && currentURI.spec == "about:downloads";
|
|
default: // Nothing
|
|
return false;
|
|
}
|
|
},
|
|
|
|
buildString: function(mode)
|
|
{
|
|
switch(mode)
|
|
{
|
|
case 0:
|
|
return this._dlCountStr;
|
|
case 1:
|
|
return ((this._dlPaused) ? this._dlCountStr : this._dlTimeStr);
|
|
default:
|
|
let compStr = this._dlCountStr;
|
|
if(!this._dlPaused)
|
|
{
|
|
compStr += " (" + this._dlTimeStr + ")";
|
|
}
|
|
return compStr;
|
|
}
|
|
}
|
|
};
|
|
|
|
function JSTransferHandler(window, downloadService)
|
|
{
|
|
this._window = window;
|
|
|
|
let api = CU.import("resource://gre/modules/Downloads.jsm", {}).Downloads;
|
|
|
|
this._activePublic = new JSTransferListener(downloadService, api.getList(api.PUBLIC), false);
|
|
this._activePrivate = new JSTransferListener(downloadService, api.getList(api.PRIVATE), true);
|
|
}
|
|
|
|
JSTransferHandler.prototype =
|
|
{
|
|
_window: null,
|
|
_activePublic: null,
|
|
_activePrivate: null,
|
|
|
|
destroy: function()
|
|
{
|
|
this._activePublic.destroy();
|
|
this._activePrivate.destroy();
|
|
|
|
["_window", "_activePublic", "_activePrivate"].forEach(function(prop)
|
|
{
|
|
delete this[prop];
|
|
}, this);
|
|
},
|
|
|
|
start: function()
|
|
{
|
|
this._activePublic.start();
|
|
this._activePrivate.start();
|
|
},
|
|
|
|
stop: function()
|
|
{
|
|
this._activePublic.stop();
|
|
this._activePrivate.stop();
|
|
},
|
|
|
|
get hasPBAPI()
|
|
{
|
|
return true;
|
|
},
|
|
|
|
openUINative: function()
|
|
{
|
|
this._window.DownloadsPanel.showPanel();
|
|
},
|
|
|
|
get isUIShowingNative()
|
|
{
|
|
return this._window.DownloadsPanel.isPanelShowing;
|
|
},
|
|
|
|
activeEntries: function()
|
|
{
|
|
return this._activePublic.downloads();
|
|
},
|
|
|
|
activePrivateEntries: function()
|
|
{
|
|
return this._activePrivate.downloads();
|
|
}
|
|
};
|
|
|
|
function JSTransferListener(downloadService, listPromise, isPrivate)
|
|
{
|
|
this._downloadService = downloadService;
|
|
this._isPrivate = isPrivate;
|
|
this._downloads = new Map();
|
|
|
|
listPromise.then(this.initList.bind(this)).then(null, CU.reportError);
|
|
}
|
|
|
|
JSTransferListener.prototype =
|
|
{
|
|
_downloadService: null,
|
|
_list: null,
|
|
_downloads: null,
|
|
_isPrivate: false,
|
|
_wantsStart: false,
|
|
|
|
initList: function(list)
|
|
{
|
|
this._list = list;
|
|
if(this._wantsStart) {
|
|
this.start();
|
|
}
|
|
|
|
this._list.getAll().then(this.initDownloads.bind(this)).then(null, CU.reportError);
|
|
},
|
|
|
|
initDownloads: function(downloads)
|
|
{
|
|
downloads.forEach(function(download)
|
|
{
|
|
this.onDownloadAdded(download);
|
|
}, this);
|
|
},
|
|
|
|
destroy: function()
|
|
{
|
|
this._downloads.clear();
|
|
|
|
["_downloadService", "_list", "_downloads"].forEach(function(prop)
|
|
{
|
|
delete this[prop];
|
|
}, this);
|
|
},
|
|
|
|
start: function()
|
|
{
|
|
if(!this._list)
|
|
{
|
|
this._wantsStart = true;
|
|
return;
|
|
}
|
|
|
|
this._list.addView(this);
|
|
},
|
|
|
|
stop: function()
|
|
{
|
|
if(!this._list)
|
|
{
|
|
this._wantsStart = false;
|
|
return;
|
|
}
|
|
|
|
this._list.removeView(this);
|
|
},
|
|
|
|
downloads: function()
|
|
{
|
|
return this._downloads.values();
|
|
},
|
|
|
|
convertToState: function(dl)
|
|
{
|
|
if(dl.succeeded)
|
|
{
|
|
return CI.nsIDownloadManager.DOWNLOAD_FINISHED;
|
|
}
|
|
if(dl.error && dl.error.becauseBlockedByParentalControls)
|
|
{
|
|
return CI.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL;
|
|
}
|
|
if(dl.error)
|
|
{
|
|
return CI.nsIDownloadManager.DOWNLOAD_FAILED;
|
|
}
|
|
if(dl.canceled && dl.hasPartialData)
|
|
{
|
|
return CI.nsIDownloadManager.DOWNLOAD_PAUSED;
|
|
}
|
|
if(dl.canceled)
|
|
{
|
|
return CI.nsIDownloadManager.DOWNLOAD_CANCELED;
|
|
}
|
|
if(dl.stopped)
|
|
{
|
|
return CI.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
|
|
}
|
|
return CI.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
|
|
},
|
|
|
|
onDownloadAdded: function(aDownload)
|
|
{
|
|
let dl = this._downloads.get(aDownload);
|
|
if(!dl)
|
|
{
|
|
dl = {};
|
|
this._downloads.set(aDownload, dl);
|
|
}
|
|
|
|
dl.state = this.convertToState(aDownload);
|
|
dl.size = aDownload.totalBytes;
|
|
dl.speed = aDownload.speed;
|
|
dl.transferred = aDownload.currentBytes;
|
|
},
|
|
|
|
onDownloadChanged: function(aDownload)
|
|
{
|
|
this.onDownloadAdded(aDownload);
|
|
|
|
if(this._isPrivate != this._downloadService.isPrivateWindow)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this._downloadService.updateStatus(aDownload.succeeded);
|
|
|
|
if(aDownload.succeeded)
|
|
{
|
|
this._downloadService.notify()
|
|
}
|
|
},
|
|
|
|
onDownloadRemoved: function(aDownload)
|
|
{
|
|
this._downloads.delete(aDownload);
|
|
}
|
|
};
|
|
|