diff --git a/common/content/commandline.js b/common/content/commandline.js index 59197c39..1a9ccb17 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -2136,7 +2136,8 @@ var ItemList = Class("ItemList", { // We need to collect all of the rescrolling functions in // one go, as the height calculation that they need to do - // would force a reflow after each DOM modification. + // would force an expensive reflow after each call due to + // DOM modifications, otherwise. this.activeGroups.filter(g => !g.collapsed) .map(g => g.rescrollFunc) .forEach(call); @@ -2270,7 +2271,7 @@ var ItemList = Class("ItemList", { getGroup: function getGroup(context) context instanceof ItemList.Group ? context : context && context.getCache("itemlist-group", - bind("Group", ItemList, this, context)), + () => ItemList.Group(this, context)), getOffset: function getOffset(tuple) tuple && this.getGroup(tuple[0]).getOffset(tuple[1]) }, { diff --git a/common/content/hints.js b/common/content/hints.js index af09ad4d..019f753e 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -332,7 +332,7 @@ var HintSession = Class("HintSession", CommandMode, { __proto__: this.Hint }); - for (let hint in values(_hints)) { + for (let hint of _hints) { let { elem, rect } = hint; if (elem.hasAttributeNS(NS, "hint")) diff --git a/common/locale/en-US/messages.properties b/common/locale/en-US/messages.properties index fc69e8fa..da27f0b1 100644 --- a/common/locale/en-US/messages.properties +++ b/common/locale/en-US/messages.properties @@ -162,13 +162,11 @@ download.nActive-1 = %S active download.almostDone = ~1 second download.unknown = Unknown -download.action.Cancel = Cancel download.action.Clear = Clear download.action.Delete = Delete -download.action.Pause = Pause +download.action.Stop = Stop download.action.Remove = Remove download.action.Resume = Resume -download.action.Retry = Retry editor.prompt.editPassword = Editing a password field externally will reveal the password. Would you like to continue? (yes/[no]): diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 75a0a920..979c491f 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2013 Kris Maglione +// Copyright (c) 2009-2014 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -6,10 +6,15 @@ var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; -Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); +function module(url) { + let obj = {}; + Cu.import(url, obj); + return obj; +} + +var { XPCOMUtils } = module("resource://gre/modules/XPCOMUtils.jsm"); try { - var ctypes; - Cu.import("resource://gre/modules/ctypes.jsm"); + var ctypes = module("resource://gre/modules/ctypes.jsm"); } catch (e) {} @@ -22,7 +27,11 @@ if (typeof XPCSafeJSObjectWrapper === "undefined") let getGlobalForObject = Cu.getGlobalForObject || (obj => obj.__parent__); -function require(module, target) JSMLoader.load(module, target); +function require(module_, target) { + if (/^[A-Za-z0-9]+:/.test(module_)) + return module(module_); + return JSMLoader.load(module_, target); +} function lazyRequire(module, names, target) { for each (let name in names) @@ -133,13 +142,16 @@ function require_(obj, name, from, targetName) { defineModule("base", { // sed -n 's/^(const|var|function) ([a-zA-Z0-9_]+).*/ "\2",/p' base.jsm | sort | fmt exports: [ - "ErrorBase", "Cc", "Ci", "Class", "Cr", "Cu", "Finished", "Module", "JSMLoader", - "Set", "Struct", "StructBase", "Timer", "UTF8", "XPCOM", "XPCOMShim", "XPCOMUtils", - "XPCSafeJSObjectWrapper", "array", "bind", "call", "callable", "ctypes", "curry", - "debuggerProperties", "defineModule", "deprecated", "endModule", "forEach", "isArray", - "isGenerator", "isinstance", "isObject", "isString", "isSubclass", "isXML", "iter", - "iterAll", "iterOwnProperties", "keys", "literal", "memoize", "octal", "properties", - "require", "set", "update", "values", "update_" + "ErrorBase", "Cc", "Ci", "Class", "Cr", "Cu", "Finished", + "Module", "JSMLoader", "RealSet", "Set", "Struct", "StructBase", + "Timer", "UTF8", "XPCOM", "XPCOMShim", "XPCOMUtils", + "XPCSafeJSObjectWrapper", "array", "bind", "call", "callable", + "ctypes", "curry", "debuggerProperties", "defineModule", + "deprecated", "endModule", "forEach", "isArray", "isGenerator", + "isinstance", "isObject", "isString", "isSubclass", "isXML", + "iter", "iterAll", "iterOwnProperties", "keys", "literal", + "memoize", "modujle", "octal", "properties", "require", "set", + "update", "values", "update_" ] }); @@ -318,9 +330,13 @@ deprecated.warn = function warn(func, name, alternative, frame) { * @returns {Generator} */ function keys(obj) iter(function keys() { - for (var k in obj) - if (hasOwnProperty.call(obj, k)) + if (isinstance(obj, ["Map"])) + for (let [k, v] of obj) yield k; + else + for (var k in obj) + if (hasOwnProperty.call(obj, k)) + yield k; }()); /** @@ -331,7 +347,10 @@ function keys(obj) iter(function keys() { * @returns {Generator} */ function values(obj) iter(function values() { - if (isinstance(obj, ["Generator", "Iterator", Iter])) + if (isinstance(obj, ["Map"])) + for (let [k, v] of obj) + yield v; + else if (isinstance(obj, ["Generator", "Iterator", Iter])) for (let k in obj) yield k; else @@ -343,6 +362,8 @@ function values(obj) iter(function values() { var forEach = deprecated("iter.forEach", function forEach() iter.forEach.apply(iter, arguments)); var iterAll = deprecated("iter", function iterAll() iter.apply(null, arguments)); +var RealSet = Set; + /** * Utility for managing sets of strings. Given an array, returns an * object with one key for each value thereof. @@ -350,7 +371,7 @@ var iterAll = deprecated("iter", function iterAll() iter.apply(null, arguments)) * @param {[string]} ary @optional * @returns {object} */ -function Set(ary) { +this.Set = function Set(ary) { let obj = {}; if (ary) for (let val in values(ary)) @@ -1377,6 +1398,10 @@ function iter(obj, iface) { if (arguments.length == 2 && iface instanceof Ci.nsIJSIID) return iter(obj).map(item => item.QueryInterface(iface)); + if (isinstance(obj, ["Map Iterator"])) + // This is stupid. + obj = { __iterator__: (function () this).bind(obj) }; + let args = arguments; let res = Iterator(obj); @@ -1388,6 +1413,9 @@ function iter(obj, iface) { })(); else if (isinstance(obj, ["Iterator", "Generator"])) ; + else if (isinstance(obj, ["Map"])) + // This is stupid. + res = (r for (r of obj)); else if (ctypes && ctypes.CData && obj instanceof ctypes.CData) { while (obj.constructor instanceof ctypes.PointerType) obj = obj.contents; diff --git a/common/modules/buffer.jsm b/common/modules/buffer.jsm index c47689ff..8856c8ad 100644 --- a/common/modules/buffer.jsm +++ b/common/modules/buffer.jsm @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott // Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2013 Kris Maglione +// Copyright (c) 2008-2014 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -16,6 +16,7 @@ lazyRequire("contexts", ["Group"]); lazyRequire("io", ["io"]); lazyRequire("finder", ["RangeFind"]); lazyRequire("overlay", ["overlay"]); +lazyRequire("promises", ["Promise", "promises"]); lazyRequire("sanitizer", ["sanitizer"]); lazyRequire("storage", ["File", "storage"]); lazyRequire("template", ["template"]); @@ -68,6 +69,50 @@ var Buffer = Module("Buffer", { ); }, + /** + * The load context of the window bound to this buffer. + */ + get loadContext() sanitizer.getContext(this.win), + + /** + * Content preference methods. + */ + prefs: Class.Memoize(() => ({ + /** + * Returns a promise for the given preference name. + * + * @param {string} pref The name of the preference to return. + * @returns {Promise} + */ + get: promises.withCallback(function get(callback, pref) { + services.contentPrefs.getByDomainAndName( + this.uri.host, pref, this.loadContext, + callback); + }), + + /** + * Sets a content preference for the given buffer. + * + * @param {string} pref The preference to set. + * @param {string} value The value to store. + */ + set: promises.withCallback(function set(callback, pref, value) { + services.contentPrefs.set( + this.uri.host, pref, value, this.loadContext, + callback); + }), + + /** + * Clear a content preference for the given buffer. + * + * @param {string} pref The preference to clear. + */ + clear: promises.withCallback(function clear(callback, pref) { + services.contentPrefs.removeByDomainAndName( + this.uri.domain, pref, this.loadContext, callback); + }), + })), + /** * Gets a content preference for the given buffer. * @@ -77,10 +122,10 @@ var Buffer = Module("Buffer", { * @returns {string|number|boolean} The value of the preference, if * callback is not provided. */ - getPref: function getPref(pref, callback) { + getPref: deprecated("prefs.get", function getPref(pref, callback) { services.contentPrefs.getPref(this.uri, pref, - sanitizer.getContext(this.win), callback); - }, + this.loadContext, callback); + }), /** * Sets a content preference for the given buffer. @@ -88,20 +133,20 @@ var Buffer = Module("Buffer", { * @param {string} pref The preference to set. * @param {string} value The value to store. */ - setPref: function setPref(pref, value) { + setPref: deprecated("prefs.set", function setPref(pref, value) { services.contentPrefs.setPref( - this.uri, pref, value, sanitizer.getContext(this.win)); - }, + this.uri, pref, value, this.loadContext); + }), /** * Clear a content preference for the given buffer. * * @param {string} pref The preference to clear. */ - clearPref: function clearPref(pref) { + clearPref: deprecated("prefs.clear", function clearPref(pref) { services.contentPrefs.removePref( - this.uri, pref, sanitizer.getContext(this.win)); - }, + this.uri, pref, this.loadContext); + }), climbUrlPath: function climbUrlPath(count) { let { dactyl } = this.modules; @@ -1244,12 +1289,12 @@ var Buffer = Module("Buffer", { if (prefs.get("browser.zoom.siteSpecific")) { var privacy = sanitizer.getContext(this.win); if (value == 1) { - this.clearPref("browser.content.full-zoom"); - this.clearPref("dactyl.content.full-zoom"); + this.prefs.clear("browser.content.full-zoom"); + this.prefs.clear("dactyl.content.full-zoom"); } else { - this.setPref("browser.content.full-zoom", value); - this.setPref("dactyl.content.full-zoom", fullZoom); + this.prefs.set("browser.content.full-zoom", value); + this.prefs.set("dactyl.content.full-zoom", fullZoom); } } @@ -1259,15 +1304,15 @@ var Buffer = Module("Buffer", { /** * Updates the zoom level of this buffer from a content preference. */ - updateZoom: util.wrapCallback(function updateZoom() { + updateZoom: promises.task(function updateZoom() { let uri = this.uri; if (prefs.get("browser.zoom.siteSpecific")) { - this.getPref("dactyl.content.full-zoom", (val) => { - if (val != null && uri.equals(this.uri) && val != prefs.get("browser.zoom.full")) - [this.contentViewer.textZoom, this.contentViewer.fullZoom] = - [this.contentViewer.fullZoom, this.contentViewer.textZoom]; - }); + let val = this.prefs.get("dactyl.content.full-zoom"); + + if (val != null && uri.equals(this.uri) && val != prefs.get("browser.zoom.full")) + [this.contentViewer.textZoom, this.contentViewer.fullZoom] = + [this.contentViewer.fullZoom, this.contentViewer.textZoom]; } }), diff --git a/common/modules/completion.jsm b/common/modules/completion.jsm index 001b267b..14b5feb7 100644 --- a/common/modules/completion.jsm +++ b/common/modules/completion.jsm @@ -733,7 +733,7 @@ var CompletionContext = Class("CompletionContext", { let alias = (prop) => { context.__defineGetter__(prop, () => this[prop]); context.__defineSetter__(prop, (val) => this[prop] = val); - } + }; alias("_cache"); alias("_completions"); alias("_generate"); diff --git a/common/modules/config.jsm b/common/modules/config.jsm index 7c5b57b9..c04a57a0 100644 --- a/common/modules/config.jsm +++ b/common/modules/config.jsm @@ -45,7 +45,7 @@ var ConfigBase = Class("ConfigBase", { * initialization code. Must call superclass's init function. */ init: function init() { - if (config.haveGecko("26")) + if (!config.haveGecko("26")) this.modules.global = this.modules.global.filter(m => m != "downloads"); // FIXME this.loadConfig(); diff --git a/common/modules/downloads.jsm b/common/modules/downloads.jsm index cbcd9a6e..63d826eb 100644 --- a/common/modules/downloads.jsm +++ b/common/modules/downloads.jsm @@ -1,4 +1,4 @@ -// Copyright (c) 2011-2013 Kris Maglione +// Copyright (c) 2011-2014 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -10,8 +10,10 @@ defineModule("downloads", { }); lazyRequire("overlay", ["overlay"]); +lazyRequire("promises", ["Task", "promises"]); -Cu.import("resource://gre/modules/DownloadUtils.jsm", this); +lazyRequire("resource://gre/modules/Downloads.jsm", ["Downloads"]); +lazyRequire("resource://gre/modules/DownloadUtils.jsm", ["DownloadUtils"]); var MAX_LOAD_TIME = 10 * 1000; @@ -22,8 +24,8 @@ var states = iter([v, k.slice(prefix.length).toLowerCase()] .toObject(); var Download = Class("Download", { - init: function init(id, list) { - this.download = services.downloadManager.getDownload(id); + init: function init(download, list) { + this.download = download; this.list = list; this.nodes = { @@ -39,11 +41,9 @@ var Download = Class("Download", { this.targetFile.path]]], ["td", { highlight: "DownloadState", key: "state" }], ["td", { highlight: "DownloadButtons Buttons" }, - ["a", { highlight: "Button", href: "javascript:0", key: "pause" }, _("download.action.Pause")], + ["a", { highlight: "Button", href: "javascript:0", key: "stop" }, _("download.action.Stop")], ["a", { highlight: "Button", href: "javascript:0", key: "remove" }, _("download.action.Remove")], ["a", { highlight: "Button", href: "javascript:0", key: "resume" }, _("download.action.Resume")], - ["a", { highlight: "Button", href: "javascript:0", key: "retry" }, _("download.action.Retry")], - ["a", { highlight: "Button", href: "javascript:0", key: "cancel" }, _("download.action.Cancel")], ["a", { highlight: "Button", href: "javascript:0", key: "delete" }, _("download.action.Delete")]], ["td", { highlight: "DownloadProgress", key: "progress" }, ["span", { highlight: "DownloadProgressHave", key: "progressHave" }], @@ -53,8 +53,8 @@ var Download = Class("Download", { ["td", { highlight: "DownloadSpeed", key: "speed" }], ["td", { highlight: "DownloadTime", key: "time" }], ["td", {}, - ["a", { highlight: "DownloadSource", key: "source", href: this.source.spec }, - this.source.spec]]], + ["a", { highlight: "DownloadSource", key: "source", href: this.source.url }, + this.source.url]]], this.list.document, this.nodes); this.nodes.launch.addEventListener("click", (event) => { @@ -68,21 +68,22 @@ var Download = Class("Download", { return this; }, + get active() !this.stopped, + + get targetFile() File(this.download.target.path), + + get displayName() this.targetFile.leafName, + get status() states[this.state], inState: function inState(states) states.indexOf(this.status) >= 0, - get alive() this.inState(["downloading", "notstarted", "paused", "queued", "scanning"]), - allowedCommands: Class.Memoize(function () let (self = this) ({ - get cancel() self.cancelable && self.inState(["downloading", "paused", "starting"]), - get delete() !this.cancel && self.targetFile.exists(), - get launch() self.targetFile.exists() && self.inState(["finished"]), - get pause() self.inState(["downloading"]), - get remove() self.inState(["blocked_parental", "blocked_policy", - "canceled", "dirty", "failed", "finished"]), - get resume() self.resumable && self.inState(["paused"]), - get retry() self.inState(["canceled", "failed"]) + get delete() !self.active && (self.targetFile.exists() || self.hasPartialData), + get launch() self.targetFile.exists() && self.succeeded, + get stop() self.active, + get remove() !self.active, + get resume() self.canceled })), command: function command(name) { @@ -91,15 +92,16 @@ var Download = Class("Download", { if (Set.has(this.commands, name)) this.commands[name].call(this); - else - services.downloadManager[name + "Download"](this.id); }, commands: { - delete: function delete_() { - this.targetFile.remove(false); + delete: promises.task(function delete_() { + if (this.hasPartialData) + yield this.removePartialData(); + else if (self.targetFile.exists()) + this.targetFile.remove(false); this.updateStatus(); - }, + }), launch: function launch() { // Behavior mimics that of the builtin Download Manager. function action() { @@ -127,18 +129,28 @@ var Download = Class("Download", { }); else action.call(this); - } + }, + resume: function resume() { + this.download.start(); + }, + remove: promises.task(function remove() { + yield this.list.list.remove(this.download); + yield this.download.finalize(true); + }), + stop: function stop() { + this.download.cancel(); + }, }, _compare: { - active: (a, b) => a.alive - b.alive, + active: (a, b) => a.active - b.active, complete: (a, b) => a.percentComplete - b.percentComplete, - date: (a, b) => a.startTime - b.startTime, + date: (a, b) => a.startTime - b.startTime, filename: (a, b) => String.localeCompare(a.targetFile.leafName, b.targetFile.leafName), - size: (a, b) => a.size - b.size, - speed: (a, b) => a.speed - b.speed, - time: (a, b) => a.timeRemaining - b.timeRemaining, - url: (a, b) => String.localeCompare(a.source.spec, b.source.spec) + size: (a, b) => a.totalBytes - b.totalBytes, + speed: (a, b) => a.speed - b.speed, + time: (a, b) => a.timeRemaining - b.timeRemaining, + url: (a, b) => String.localeCompare(a.source.url, b.source.url) }, compare: function compare(other) values(this.list.sortOrder).map(function (order) { @@ -152,17 +164,17 @@ var Download = Class("Download", { updateProgress: function updateProgress() { let self = this.__proto__; - if (this.amountTransferred === this.size) { + if (!this.active) { this.nodes.speed.textContent = ""; this.nodes.time.textContent = ""; } else { this.nodes.speed.textContent = util.formatBytes(this.speed, 1, true) + "/s"; - if (this.speed == 0 || this.size == 0) + if (this.speed == 0 || !this.hasProgress) this.nodes.time.textContent = _("download.unknown"); else { - let seconds = (this.size - this.amountTransferred) / this.speed; + let seconds = (this.totalBytes - this.currentBytes) / this.speed; [, self.timeRemaining] = DownloadUtils.getTimeLeft(seconds, this.timeRemaining); if (this.timeRemaining) this.nodes.time.textContent = util.formatSeconds(this.timeRemaining); @@ -171,17 +183,20 @@ var Download = Class("Download", { } } - let total = this.nodes.progressTotal.textContent = this.size || !this.nActive ? util.formatBytes(this.size, 1, true) - : _("download.unknown"); - let suffix = RegExp(/( [a-z]+)?$/i.exec(total)[0] + "$"); - this.nodes.progressHave.textContent = util.formatBytes(this.amountTransferred, 1, true).replace(suffix, ""); + let total = this.nodes.progressTotal.textContent = + this.hasProgress && (this.totalBytes || !this.nActive) + ? util.formatBytes(this.totalBytes, 1, true) + : _("download.unknown"); - this.nodes.percent.textContent = this.size ? Math.round(this.amountTransferred * 100 / this.size) + "%" : ""; + let suffix = RegExp(/( [a-z]+)?$/i.exec(total)[0] + "$"); + this.nodes.progressHave.textContent = util.formatBytes(this.currentBytes, 1, true).replace(suffix, ""); + + this.nodes.percent.textContent = this.hasProgress ? this.progress + "%" : ""; }, updateStatus: function updateStatus() { - this.nodes.row[this.alive ? "setAttribute" : "removeAttribute"]("active", "true"); + this.nodes.row[this.active ? "setAttribute" : "removeAttribute"]("active", "true"); this.nodes.row.setAttribute("status", this.status); this.nodes.state.textContent = util.capitalize(this.status); @@ -193,14 +208,6 @@ var Download = Class("Download", { this.updateProgress(); } }); -Object.keys(XPCOMShim([Ci.nsIDownload])).forEach(function (key) { - if (!(key in Download.prototype)) - Object.defineProperty(Download.prototype, key, { - get: function get() this.download[key], - set: function set(val) this.download[key] = val, - configurable: true - }); -}); var DownloadList = Class("DownloadList", XPCOM([Ci.nsIDownloadProgressListener, @@ -213,12 +220,13 @@ var DownloadList = Class("DownloadList", this.nodes = { commandTarget: this }; - this.downloads = {}; + this.downloads = Map(); }, cleanup: function cleanup() { - this.observe.unregister(); - services.downloadManager.removeListener(this); + if (this.list) + this.list.removeView(this); + this.dead = true; }, message: Class.Memoize(function () { @@ -258,40 +266,44 @@ var DownloadList = Class("DownloadList", this.index = Array.indexOf(this.nodes.list.childNodes, this.nodes.head); - let start = Date.now(); - for (let row in iter(services.downloadManager.DBConnection - .createStatement("SELECT id FROM moz_downloads"))) { - if (Date.now() - start > MAX_LOAD_TIME) { - util.dactyl.warn(_("download.givingUpAfter", (Date.now() - start) / 1000)); - break; - } - this.addDownload(row.id); - } - this.update(); + Task.spawn(function () { + this.list = yield Downloads.getList(Downloads.ALL); - util.addObserver(this); - services.downloadManager.addListener(this); + let start = Date.now(); + for (let download of yield this.list.getAll()) { + if (Date.now() - start > MAX_LOAD_TIME) { + util.dactyl.warn(_("download.givingUpAfter", (Date.now() - start) / 1000)); + break; + } + this.addDownload(download); + } + this.update(); + + if (!this.dead) + this.list.addView(this); + }.bind(this)); return this.nodes.list; }), - addDownload: function addDownload(id) { - if (!(id in this.downloads)) { - let download = Download(id, this); + addDownload: function addDownload(download) { + if (!this.downloads.has(download)) { + download = Download(download, this); if (this.filter && download.displayName.indexOf(this.filter) === -1) return; - this.downloads[id] = download; - let index = values(this.downloads).sort((a, b) => a.compare(b)) - .indexOf(download); + this.downloads.set(download.download, download); + let index = values(this.downloads).toArray() + .sort((a, b) => a.compare(b)) + .indexOf(download); this.nodes.list.insertBefore(download.nodes.row, this.nodes.list.childNodes[index + this.index + 1]); } }, - removeDownload: function removeDownload(id) { - if (id in this.downloads) { - this.nodes.list.removeChild(this.downloads[id].nodes.row); - delete this.downloads[id]; + removeDownload: function removeDownload(download) { + if (this.downloads.has(download)) { + this.nodes.list.removeChild(this.downloads.get(download).nodes.row); + delete this.downloads.delete(download); } }, @@ -301,17 +313,17 @@ var DownloadList = Class("DownloadList", }, allowedCommands: Class.Memoize(function () let (self = this) ({ - get clear() values(self.downloads).some(dl => dl.allowedCommands.remove) + get clear() iter(self.downloads.values()).some(dl => dl.allowedCommands.remove) })), commands: { clear: function () { - services.downloadManager.cleanUp(); + this.list.removeFinished(); } }, sort: function sort() { - let list = values(this.downloads).sort((a, b) => a.compare(b)); + let list = iter(this.downloads.values()).sort((a, b) => a.compare(b)); for (let [i, download] in iter(list)) if (this.nodes.list.childNodes[i + 1] != download.nodes.row) @@ -335,16 +347,19 @@ var DownloadList = Class("DownloadList", timeRemaining: Infinity, updateProgress: function updateProgress() { - let downloads = values(this.downloads).toArray(); - let active = downloads.filter(d => d.alive); + let downloads = iter(this.downloads.values()).toArray(); + let active = downloads.filter(d => d.active); let self = Object.create(this); - for (let prop in values(["amountTransferred", "size", "speed", "timeRemaining"])) + for (let prop in values(["currentBytes", "totalBytes", "speed", "timeRemaining"])) this[prop] = active.reduce((acc, dl) => dl[prop] + acc, 0); + this.hasProgress = active.every(d => d.hasProgress); + this.progress = Math.round((this.currentBytes / this.totalBytes) * 100); + this.nActive = active.length; + Download.prototype.updateProgress.call(self); - this.nActive = active.length; if (active.length) this.nodes.total.textContent = _("download.nActive", active.length); else for (let key in values(["total", "percent", "speed", "time"])) @@ -354,68 +369,87 @@ var DownloadList = Class("DownloadList", this.sort(); }, - observers: { - "download-manager-remove-download": function (id) { - if (id == null) - id = [k for ([k, dl] in iter(this.downloads)) if (dl.allowedCommands.remove)]; - else - id = [id.QueryInterface(Ci.nsISupportsPRUint32).data]; + onDownloadAdded: function onDownloadAdded(download) { + this.addDownload(download); - Array.concat(id).map(this.closure.removeDownload); - this.update(); - } + this.modules.mow.resize(false); + this.nodes.list.scrollIntoView(false); }, - onDownloadStateChange: function (state, download) { - try { - if (download.id in this.downloads) - this.downloads[download.id].updateStatus(); - else { - this.addDownload(download.id); + onDownloadRemoved: function onDownloadRemoved(download) { + this.removeDownload(download); + }, + + onDownloadChanged: function onDownloadChanged(download) { + if (this.downloads.has(download)) { + download = this.downloads.get(download) + + download.updateStatus(); + download.updateProgress(); - this.modules.mow.resize(false); - this.nodes.list.scrollIntoView(false); - } this.update(); if (this.shouldSort("active")) this.sort(); } - catch (e) { - util.reportError(e); - } - }, - - onProgressChange: function (webProgress, request, - curProgress, maxProgress, - curTotalProgress, maxTotalProgress, - download) { - try { - if (download.id in this.downloads) - this.downloads[download.id].updateProgress(); - this.updateProgress(); - } - catch (e) { - util.reportError(e); - } } }); +["canceled", + "contentType", + "currentBytes", + "error", + "hasPartialData", + "hasProgress", + "launchWhenSucceeded", + "launcherPath", + "progress", + "saver", + "source", + "speed", + "startTime", + "stopped", + "succeeded", + "target", + "totalBytes", + "tryToKeepPartialData"].forEach(key => { + if (!(key in Download.prototype)) + Object.defineProperty(Download.prototype, key, { + get: function get() this.download[key], + set: function set(val) this.download[key] = val, + configurable: true + }); +}); -var Downloads = Module("downloads", XPCOM(Ci.nsIDownloadProgressListener), { + +var Downloads_ = Module("downloads", XPCOM(Ci.nsIDownloadProgressListener), { init: function () { - services.downloadManager.addListener(this); + Downloads.getList(Downloads.ALL).then(list => { + this.list = list; + if (!this.dead) + this.list.addView(this); + }); }, cleanup: function destroy() { - services.downloadManager.removeListener(this); + if (this.list) + this.list.removeView(this); + this.dead = true; }, - onDownloadStateChange: function (state, download) { - if (download.state == services.downloadManager.DOWNLOAD_FINISHED) { - let url = download.source.spec; - let title = download.displayName; - let file = download.targetFile.path; - let size = download.size; + onDownloadAdded: function onDownloadAdded(download) { + }, + + onDownloadRemoved: function onDownloadRemoved(download) { + }, + + onDownloadChanged: function onDownloadChanged(download) { + if (download.succeeded) { + let target = File(download.target.path); + + let url = download.source.url; + let title = target.leafName; + let file = target.path; + let size = download.totalBytes; overlay.modules.forEach(function (modules) { modules.dactyl.echomsg({ domains: [util.getHost(url)], message: _("io.downloadFinished", title, file) }, @@ -451,7 +485,7 @@ var Downloads = Module("downloads", XPCOM(Ci.nsIDownloadProgressListener), { commands.add(["dlc[lear]"], "Clear completed downloads", - function (args) { services.downloadManager.cleanUp(); }); + function (args) { downloads.list.removeFinished(); }); }, options: function initOptions(dactyl, modules, window) { const { options } = modules; diff --git a/common/modules/javascript.jsm b/common/modules/javascript.jsm index dcf206bf..47f77ad4 100644 --- a/common/modules/javascript.jsm +++ b/common/modules/javascript.jsm @@ -336,9 +336,6 @@ var JavaScript = Module("javascript", { _complete: function (objects, key, compl, string, last) { const self = this; - if (!getOwnPropertyNames && !services.debugger.isOn && !this.context.message) - this.context.message = /*L*/"For better completion data, please enable the JavaScript debugger (:set jsdebugger)"; - let base = this.context.fork("js", this._top.offset); base.forceAnchored = true; base.filter = last == null ? key : string; diff --git a/common/modules/promises.jsm b/common/modules/promises.jsm new file mode 100644 index 00000000..25aba2f5 --- /dev/null +++ b/common/modules/promises.jsm @@ -0,0 +1,48 @@ +// Copyright (c) 2014 Kris Maglione +// +// This work is licensed for reuse under an MIT license. Details are +// given in the LICENSE.txt file included with this file. +"use strict"; + +defineModule("promises", { + exports: ["Promise", "Task", "promises"], + require: [] +}); + +lazyRequire("resource://gre/modules/Promise.jsm", ["Promise"]); +lazyRequire("resource://gre/modules/Task.jsm", ["Task"]); + +var Promises = Module("Promises", { + /** + * Wraps the given function so that each call spawns a Task. + * + * @param {function} fn The function to wrap. + * @returns {function} + */ + task: function task(fn) { + return function task_(...args) { + return Task.spawn(fn.bind.apply(fn, [this].concat(args))); + } + }, + + /** + * Wraps the given function so that its first argument is a + * callback which, when called, resolves the returned promise. + * + * @param {function} fn The function to wrap. + * @returns {Promise} + */ + withCallback: function withCallback(fn) { + return function wrapper(...args) { + let deferred = Promise.defer(); + function callback(arg) { + deferred.resolve(arg); + } + return fn.apply(this, [callback].concat(args)); + } + }, +}); + +endModule(); + +// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: