diff --git a/common/content/browser.js b/common/content/browser.js
index 826c2a6b..1e4d2dc8 100644
--- a/common/content/browser.js
+++ b/common/content/browser.js
@@ -133,7 +133,7 @@ var Browser = Module("browser", {
},
commands: function () {
- commands.add(["downl[oads]", "dl"],
+ commands.add(["old-downl[oads]", "old-dl"],
"Show progress of current downloads",
function () {
dactyl.open("chrome://mozapps/content/downloads/downloads.xul",
diff --git a/common/content/buffer.js b/common/content/buffer.js
index fe3adeaf..fea890f6 100644
--- a/common/content/buffer.js
+++ b/common/content/buffer.js
@@ -786,6 +786,10 @@ var Buffer = Module("buffer", {
var persist = services.Persist();
persist.persistFlags = persist.PERSIST_FLAGS_FROM_CACHE
| persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
+
+ persist.progressListener = new window.DownloadListener(window,
+ services.Transfer(uri, services.io.newFileURI(file), "",
+ null, null, null, persist));
persist.saveURI(uri, null, null, null, null, file);
}, {
autocomplete: true,
diff --git a/common/content/commandline.js b/common/content/commandline.js
index aa1b5c8c..6ef6f71c 100644
--- a/common/content/commandline.js
+++ b/common/content/commandline.js
@@ -265,10 +265,12 @@ var CommandWidgets = Class("CommandWidgets", {
while (elem.contentDocument.documentURI != elem.getAttribute("src") ||
["viewable", "complete"].indexOf(elem.contentDocument.readyState) < 0)
util.threadYield();
- return elem;
+ res = res || (processor || util.identity).call(self, elem);
+ return res;
}
});
- return Class.replaceProperty(this, name, (processor || util.identity).call(this, this[name]))
+ let res, self = this;
+ return Class.replaceProperty(this, name, this[name])
},
get completionList() this._whenReady("completionList", "dactyl-completions"),
@@ -748,8 +750,15 @@ var CommandLine = Module("commandline", {
XML.ignoreWhitespace = false;
XML.prettyPrinting = false;
let style = typeof str === "string" ? "pre" : "nowrap";
- this._lastMowOutput =
{str}
;
- let output = util.xmlToDom(this._lastMowOutput, doc);
+ if (callable(str)) {
+ this._lastMowOutput = null;
+ var output = util.xmlToDom(, doc);
+ output.appendChild(str(doc));
+ }
+ else {
+ this._lastMowOutput =
{str}
;
+ var output = util.xmlToDom(this._lastMowOutput, doc);
+ }
// FIXME: need to make sure an open MOW is closed when commands
// that don't generate output are executed
@@ -824,7 +833,7 @@ var CommandLine = Module("commandline", {
let single = flags & (this.FORCE_SINGLELINE | this.DISALLOW_MULTILINE);
let action = this._echoLine;
- if ((flags & this.FORCE_MULTILINE) || (/\n/.test(str) || typeof str == "xml") && !(flags & this.FORCE_SINGLELINE))
+ if ((flags & this.FORCE_MULTILINE) || (/\n/.test(str) || !isString(str)) && !(flags & this.FORCE_SINGLELINE))
action = this._echoMultiline;
if (single)
@@ -1073,63 +1082,69 @@ var CommandLine = Module("commandline", {
// FIXME: if 'more' is set and the MOW is not scrollable we should still
// allow a down motion after an up rather than closing
onMultilineOutputEvent: function onMultilineOutputEvent(event) {
- const KILL = false, PASS = true;
+ try {
+ const KILL = false, PASS = true;
- let win = this.widgets.multilineOutput.contentWindow;
- let elem = win.document.documentElement;
+ let win = this.widgets.multilineOutput.contentWindow;
+ let elem = win.document.documentElement;
- let key = events.toString(event);
+ let key = events.toString(event);
- function openLink(where) {
- event.preventDefault();
- dactyl.open(event.target.href, where);
- }
-
- // TODO: Wouldn't multiple handlers be cleaner? --djk
- if (event.type == "click" && event.target instanceof HTMLAnchorElement) {
-
- let command = event.originalTarget.getAttributeNS(NS.uri, "command");
- if (command && dactyl.commands[command]) {
+ const openLink = function openLink(where) {
event.preventDefault();
- return dactyl.withSavedValues(["forceNewTab"], function () {
- dactyl.forceNewTab = event.ctrlKey || event.shiftKey || event.button == 1;
- return dactyl.commands[command](event);
- });
+ dactyl.open(event.target.href, where);
}
- switch (key) {
- case "
":
- event.preventDefault();
- openLink(dactyl.CURRENT_TAB);
- return KILL;
- case "":
- case "":
- case "":
- openLink({ where: dactyl.NEW_TAB, background: true });
- return KILL;
- case "":
- case "":
- case "":
- openLink({ where: dactyl.NEW_TAB, background: false });
- return KILL;
- case "":
- openLink(dactyl.NEW_WINDOW);
- return KILL;
+ // TODO: Wouldn't multiple handlers be cleaner? --djk
+ if (event.type == "click" && (event.target instanceof HTMLAnchorElement ||
+ event.originalTarget.hasAttributeNS(NS, "command"))) {
+
+ let command = event.originalTarget.getAttributeNS(NS, "command");
+ if (command && dactyl.commands[command]) {
+ event.preventDefault();
+ return dactyl.withSavedValues(["forceNewTab"], function () {
+ dactyl.forceNewTab = event.ctrlKey || event.shiftKey || event.button == 1;
+ return dactyl.commands[command](event);
+ });
+ }
+
+ switch (key) {
+ case "":
+ event.preventDefault();
+ openLink(dactyl.CURRENT_TAB);
+ return KILL;
+ case "":
+ case "":
+ case "":
+ openLink({ where: dactyl.NEW_TAB, background: true });
+ return KILL;
+ case "":
+ case "":
+ case "":
+ openLink({ where: dactyl.NEW_TAB, background: false });
+ return KILL;
+ case "":
+ openLink(dactyl.NEW_WINDOW);
+ return KILL;
+ }
+ return PASS;
}
- return PASS;
+
+ if (event instanceof MouseEvent)
+ return KILL;
+
+ const atEnd = function atEnd(dir) !Buffer.isScrollable(elem, dir || 1);
+
+ if (!options["more"] || atEnd(1)) {
+ modes.pop();
+ events.feedkeys(key);
+ }
+ else
+ commandline.updateMorePrompt(false, true);
}
-
- if (event instanceof MouseEvent)
- return KILL;
-
- function atEnd(dir) !Buffer.isScrollable(elem, dir || 1);
-
- if (!options["more"] || atEnd(1)) {
- modes.pop();
- events.feedkeys(key);
+ catch (e) {
+ util.reportError(e);
}
- else
- commandline.updateMorePrompt(false, true);
return PASS;
},
diff --git a/common/content/dactyl.js b/common/content/dactyl.js
index 1acf1810..da8fbfb8 100644
--- a/common/content/dactyl.js
+++ b/common/content/dactyl.js
@@ -53,16 +53,18 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
},
observers: {
- "dactyl-cleanup": function () {
+ "dactyl-cleanup": function dactyl_cleanup() {
let modules = dactyl.modules;
for (let name in values(Object.getOwnPropertyNames(modules).reverse())) {
let mod = Object.getOwnPropertyDescriptor(modules, name).value;
if (mod instanceof Class) {
if ("cleanup" in mod)
- mod.cleanup();
+ this.trapErrors(mod.cleanup, mod);
if ("destroy" in mod)
- mod.destroy();
+ this.trapErrors(mod.destroy, mod);
+ if ("INIT" in mod && "cleanup" in mod.INIT)
+ this.trapErrors(mod.cleanup, mod, dactyl, modules, window);
}
}
@@ -360,6 +362,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
},
userEval: function (str, context, fileName, lineNumber) {
+ if (jsmodules.__proto__ != window)
+ str = "with (window) { with (modules) { this.eval(" + str.quote() + ") } }";
+
if (fileName == null)
if (io.sourcing && io.sourcing.file[0] !== "[")
({ file: fileName, line: lineNumber }) = io.sourcing;
@@ -389,9 +394,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
if (!context)
context = _userContext;
- if (window.isPrototypeOf(modules))
- return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber);
- return Cu.evalInSandbox("with (window) { with (modules) { this.eval(" + str.quote() + ") } }", context, "1.8", fileName, lineNumber);
+ return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber);
},
/**
@@ -2178,7 +2181,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
let init = services.environment.get(config.idName + "_INIT");
let rcFile = io.getRCFile("~");
- if (dactyl.userEval('typeof document') === "undefined")
+ if (dactyl.userEval("typeof document", null, "test.js") === "undefined")
jsmodules.__proto__ = XPCSafeJSObjectWrapper(window);
try {
diff --git a/common/content/modes.js b/common/content/modes.js
index c7b31a82..8a1694cd 100644
--- a/common/content/modes.js
+++ b/common/content/modes.js
@@ -175,6 +175,9 @@ var Modes = Module("modes", {
}
});
},
+ cleanup: function () {
+ modes.reset();
+ },
_getModeMessage: function () {
// when recording a macro
diff --git a/common/locale/en-US/gui.xml b/common/locale/en-US/gui.xml
index d38183d2..02afea01 100644
--- a/common/locale/en-US/gui.xml
+++ b/common/locale/en-US/gui.xml
@@ -70,9 +70,8 @@
:downloads
- Show progress of current downloads. Open the standard &dactyl.host;
- download dialog in a new tab. Here, downloads can be paused,
- resumed, and canceled.
+ Show progress of current downloads. Here, downloads can
+ be paused, resumed, and canceled.
diff --git a/common/modules/base.jsm b/common/modules/base.jsm
index 68e875f0..bbc3e47a 100644
--- a/common/modules/base.jsm
+++ b/common/modules/base.jsm
@@ -737,9 +737,9 @@ Class.memoize = function memoize(getter)
configurable: true,
enumerable: true,
init: function (key) {
- this.get = function replace() (
- Class.replaceProperty(this, key, null),
- Class.replaceProperty(this, key, getter.call(this, key)))
+ this.get = function replace() let (obj = this.instance || this) (
+ Class.replaceProperty(obj, key, null),
+ Class.replaceProperty(obj, key, getter.call(this, key)))
}
});
@@ -1196,6 +1196,9 @@ update(iter, {
return undefined;
},
+ sort: function sort(iter, fn, self)
+ array(this.toArray(iter).sort(fn, self)),
+
uniq: function uniq(iter) {
let seen = {};
for (let item in iter)
diff --git a/common/modules/config.jsm b/common/modules/config.jsm
index 17378772..0c86d3d9 100644
--- a/common/modules/config.jsm
+++ b/common/modules/config.jsm
@@ -426,9 +426,16 @@ var ConfigBase = Class("ConfigBase", {
Keyword color: red;
Tag color: blue;
- Usage position: relative; padding-right: 2em;
- Usage>LineInfo position: absolute; left: 100%; padding: 1ex; margin: -1ex -1em; background: rgba(255, 255, 255, .8); border-radius: 1ex;
- Usage:not(:hover)>LineInfo opacity: 0; left: 0; width: 1px; height: 1px; overflow: hidden;
+ Link position: relative; padding-right: 2em;
+ Link:not(:hover)>LinkInfo opacity: 0; left: 0; width: 1px; height: 1px; overflow: hidden;
+ LinkInfo {
+ position: absolute;
+ left: 100%;
+ padding: 1ex;
+ margin: -1ex -1em;
+ background: rgba(255, 255, 255, .8);
+ border-radius: 1ex;
+ }
StatusLine;;;FontFixed {
-moz-appearance: none !important;
@@ -490,6 +497,30 @@ var ConfigBase = Class("ConfigBase", {
HintActive;;* background-color: #88FF00 !important; color: black !important;
HintImage;;* opacity: .5 !important;
+ Button display: inline-block; font-weight: bold; cursor: pointer;
+ Button:hover text-decoration: underline;
+ Button[collapsed] visibility: collapse; width: 0;
+ Button::before content: "["; color: gray; text-decoration: none !important;
+ Button::after content: "]"; color: gray; text-decoration: none !important;
+ Button:not([collapsed]) ~ Button:not([collapsed])::before content: "/[";
+
+ Downloads display: table; margin: 0; padding: 0;
+ DownloadHead;;;CompTitle display: table-row;
+ DownloadHead>*;;;DownloadCell display: table-cell;
+
+ Download display: table-row;
+
+ DownloadCell display: table-cell; padding: 0 1ex;
+ DownloadButtons;;;DownloadCell
+ DownloadPercent;;;DownloadCell
+ DownloadProgress;;;DownloadCell
+ DownloadProgressHave
+ DownloadProgressTotal
+ DownloadSource;;;DownloadCell,URL
+ DownloadState;;;DownloadCell
+ DownloadTime;;;DownloadCell
+ DownloadTitle;;;DownloadCell,URL
+
//
]]>>, /
/g, "\n")),
diff --git a/common/modules/downloads.jsm b/common/modules/downloads.jsm
new file mode 100644
index 00000000..11592d0e
--- /dev/null
+++ b/common/modules/downloads.jsm
@@ -0,0 +1,268 @@
+// Copyright (c) 2011 by 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";
+
+Components.utils.import("resource://dactyl/bootstrap.jsm");
+defineModule("downloads", {
+ exports: ["Download", "Downloads", "downloads"],
+ use: ["io", "services", "template", "util"]
+}, this);
+
+Cu.import("resource://gre/modules/DownloadUtils.jsm", this);
+
+let prefix = "DOWNLOAD_";
+var states = iter([v, k.slice(prefix.length).toLowerCase()]
+ for ([k, v] in Iterator(Ci.nsIDownloadManager))
+ if (k.indexOf(prefix) == 0))
+ .toObject();
+
+var Download = Class("Download", {
+ init: function init(id, document) {
+ let self = XPCSafeJSObjectWrapper(services.downloadManager.getDownload(id));
+ self.__proto__ = this;
+ this.instance = this;
+
+ this.nodes = {};
+ util.xmlToDom(
+
+
+
+ {self.displayName}
+ {self.targetFile.path}
+
+
+
+
+ Pause
+ Remove
+ Resume
+ Retry
+ Cancel
+ Delete
+
+
+ /
+
+
+
+ {self.source.spec}
+ ,
+ document, this.nodes);
+
+ for (let [key, node] in Iterator(this.nodes)) {
+ node.dactylDownload = self;
+ if (node.getAttributeNS(NS, "highlight") == "Button") {
+ node.setAttributeNS(NS, "command", "download.command");
+ update(node, {
+ set collapsed(collapsed) {
+ if (collapsed)
+ this.setAttribute("collapsed", "true");
+ else
+ this.removeAttribute("collapsed");
+ },
+ get collapsed() !!this.getAttribute("collapsed")
+ });
+ }
+ }
+
+ self.updateStatus();
+ return self;
+ },
+
+ get status() states[this.state],
+
+ inState: function inState(states) states.indexOf(this.status) >= 0,
+
+ get alive() this.inState(["downloading", "notstarted", "paused", "queued", "scanning"]),
+
+ allowed: Class.memoize(function () let (self = this) ({
+ get cancel() self.cancelable && self.inState(["downloading", "paused", "starting"]),
+ get delete() !this.cancel && self.targetFile.exists(),
+ 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"])
+ })),
+
+ command: function command(name) {
+ util.assert(set.has(this.allowed, name), "Unknown command");
+ util.assert(this.allowed[name], "Command not allowed");
+
+ 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);
+ this.updateStatus();
+ }
+ },
+
+ compare: function compare(other) String.localeCompare(this.displayName, other.displayName),
+
+ timeRemaining: Infinity,
+
+ updateProgress: function updateProgress() {
+ let self = this.__proto__;
+
+ if (this.amountTransferred === this.size)
+ this.nodes.time.textContent = "";
+ else if (this.speed == 0 || this.size == 0)
+ this.nodes.time.textContent = "Unknown";
+ else {
+ let seconds = (this.size - this.amountTransferred) / this.speed;
+ [, self.timeRemaining] = DownloadUtils.getTimeLeft(seconds, this.timeRemaining);
+ if (this.timeRemaining)
+ this.nodes.time.textContent = util.formatSeconds(this.timeRemaining);
+ else
+ this.nodes.time.textContent = "~1 second";
+ }
+ let total = this.nodes.progressTotal.textContent = this.size ? util.formatBytes(this.size, 1, true) : "Unknown";
+ let suffix = RegExp(/( [a-z]+)?$/i.exec(total)[0] + "$");
+ this.nodes.progressHave.textContent = util.formatBytes(this.amountTransferred, 1, true).replace(suffix, "");
+
+ this.nodes.percent.textContent = this.size ? Math.round(this.amountTransferred * 100 / this.size) + "%" : "";
+ },
+
+ updateStatus: function updateStatus() {
+
+ this.nodes.state.textContent = util.capitalize(this.status);
+ for (let [command, enabled] in Iterator(this.allowed))
+ this.nodes[command].collapsed = !enabled;
+ this.updateProgress();
+ }
+});
+
+var DownloadList = Class("DownloadList",
+ XPCOM([Ci.nsIDownloadProgressListener,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]), {
+ init: function init(document, modules) {
+ this.modules = modules;
+ this.document = document;
+ this.nodes = {};
+ util.xmlToDom(
+ -
+ Title
+ Status
+
+ Progress
+
+ Time remaining
+ Source
+
+
, this.document, this.nodes);
+
+ this.downloads = {};
+ for (let row in iter(services.downloadManager.DBConnection
+ .createStatement("SELECT id FROM moz_downloads")))
+ this.addDownload(row.id);
+
+ util.addObserver(this);
+ services.downloadManager.addListener(this);
+ },
+ cleanup: function cleanup() {
+ this.observe.unregister();
+ services.downloadManager.removeListener(this);
+ },
+
+ addDownload: function addDownload(id) {
+ if (!(id in this.downloads)) {
+ this.downloads[id] = Download(id, this.document);
+
+ let index = values(this.downloads).sort(function (a, b) a.compare(b))
+ .indexOf(this.downloads[id]);
+
+ this.nodes.list.insertBefore(this.downloads[id].nodes.row,
+ this.nodes.list.childNodes[index + 1]);
+ }
+ },
+ removeDownload: function removeDownload(id) {
+ if (id in this.downloads) {
+ this.nodes.list.removeChild(this.downloads[id].nodes.row);
+ delete this.downloads[id];
+ }
+ },
+
+ leave: function leave(stack) {
+ if (stack.pop)
+ this.cleanup();
+ },
+
+ observers: {
+ "download-manager-remove-download": function (id) {
+ if (id == null)
+ id = [k for ([k, dl] in iter(this.downloads)) if (dl.allowed.remove)];
+ else
+ id = [id.QueryInterface(Ci.nsISupportsPRUint32).data];
+
+ Array.concat(id).map(this.closure.removeDownload);
+ }
+ },
+
+ onDownloadStateChange: function (state, download) {
+ try {
+ if (download.id in this.downloads)
+ this.downloads[download.id].updateStatus();
+ else {
+ this.addDownload(download.id);
+
+ this.modules.commandline.updateOutputHeight(true);
+ this.nodes.list.scrollIntoView(false);
+ }
+ }
+ 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();
+ }
+ catch (e) {
+ util.reportError(e);
+ }
+ }
+});
+
+var Downloads = Module("downloads", {
+}, {
+}, {
+ commands: function (dactyl, modules, window) {
+ const { commands } = modules;
+
+ commands.add(["downl[oads]", "dl"],
+ "Display the downloads list",
+ function (args) {
+ modules.commandline.echo(function (doc) {
+ let downloads = DownloadList(doc, modules);
+ // Temporary and dangerous hack:
+ modules.modes.getStack(0).params = downloads;
+ return downloads.nodes.list;
+ });
+ });
+ },
+ dactyl: function (dactyl, modules, window) {
+ dactyl.commands["download.command"] = function (event) {
+ let elem = event.originalTarget;
+ elem.dactylDownload.command(elem.getAttribute("key"));
+ }
+ }
+});
+
+endModule();
+
+// catch(e){ if (isString(e)) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
+
+// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
diff --git a/common/modules/highlight.jsm b/common/modules/highlight.jsm
index 08fc7633..e982d09e 100644
--- a/common/modules/highlight.jsm
+++ b/common/modules/highlight.jsm
@@ -92,7 +92,8 @@ var Highlights = Module("Highlight", {
keys: function keys() Object.keys(this.highlight).sort(),
- __iterator__: function () values(this.highlight),
+ __iterator__: function () values(this.highlight).sort(function (a, b) String.localeCompare(a.class, b.class))
+ .iterValues(),
_create: function (agent, args) {
let obj = Highlight.apply(Highlight, args);
@@ -318,7 +319,7 @@ var Highlights = Module("Highlight", {
if (!modify)
modules.commandline.commandOutput(
template.tabular(["Key", "Sample", "Link", "CSS"],
- ["padding: 0 1em 0 0; vertical-align: top",
+ ["padding: 0 1em 0 0; vertical-align: top; max-width: 16em; overflow: hidden;",
"text-align: center"],
([h.class,
XXX,
diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm
index 6ac9972e..7da5e57f 100644
--- a/common/modules/overlay.jsm
+++ b/common/modules/overlay.jsm
@@ -149,6 +149,7 @@ var Overlay = Module("Overlay", {
["base",
"completion",
"config",
+ "downloads",
"javascript",
"overlay",
"prefs",
diff --git a/common/modules/services.jsm b/common/modules/services.jsm
index 5e461ed5..5d1bc915 100644
--- a/common/modules/services.jsm
+++ b/common/modules/services.jsm
@@ -79,6 +79,7 @@ var Services = Module("Services", {
[Ci.nsIChannel, Ci.nsIInputStreamChannel, Ci.nsIRequest], "setURI");
this.addClass("String", "@mozilla.org/supports-string;1", Ci.nsISupportsString, "data");
this.addClass("StringStream", "@mozilla.org/io/string-input-stream;1", Ci.nsIStringInputStream, "data");
+ this.addClass("Transfer", "@mozilla.org/transfer;1", Ci.nsITransfer, "init");
this.addClass("Timer", "@mozilla.org/timer;1", Ci.nsITimer, "initWithCallback");
this.addClass("StreamCopier", "@mozilla.org/network/async-stream-copier;1",Ci.nsIAsyncStreamCopier, "init");
this.addClass("Xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", Ci.nsIXMLHttpRequest);
diff --git a/common/modules/storage.jsm b/common/modules/storage.jsm
index 3c25b31d..03ecd413 100644
--- a/common/modules/storage.jsm
+++ b/common/modules/storage.jsm
@@ -259,9 +259,15 @@ var Storage = Module("Storage", {
}
}, {
}, {
- init: function (dactyl, modules) {
+ init: function init(dactyl, modules) {
+ init.superapply(this, arguments);
storage.infoPath = File(modules.IO.runtimePath.replace(/,.*/, ""))
.child("info").child(dactyl.profileName);
+ },
+
+ cleanup: function (dactyl, modules, window) {
+ delete window.dactylStorageRefs;
+ this.removeDeadObservers();
}
});
diff --git a/common/modules/template.jsm b/common/modules/template.jsm
index 20ff431d..53aaf216 100644
--- a/common/modules/template.jsm
+++ b/common/modules/template.jsm
@@ -331,11 +331,11 @@ var Template = Module("Template", {
this.map(iter, function (item)
|
- {
+ {
let (name = item.name || item.names[0], frame = item.definedAt)
!frame ? name :
template.helpLink(help(item), name, "Title") +
- Defined at {sourceLink(frame)}
+ Defined at {sourceLink(frame)}
}
|
{desc(item)} |
diff --git a/common/modules/util.jsm b/common/modules/util.jsm
index 92ac06c2..bdea56e6 100644
--- a/common/modules/util.jsm
+++ b/common/modules/util.jsm
@@ -135,6 +135,13 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
throw FailedAssertion(message, 1);
},
+ /**
+ * Capitalizes the first character of the given string.
+ * @param {string} str The string to capitalize
+ * @returns {string}
+ */
+ capitalize: function capitalize(str) str && str[0].toUpperCase() + str.slice(1),
+
/**
* Returns a RegExp object that matches characters specified in the range
* expression *list*, or signals an appropriate error if *list* is invalid.
@@ -499,9 +506,10 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
stackLines: function (stack) {
let lines = [];
- let match, re = /([^]*?)(@[^@\n]*)(?:\n|$)/g;
+ let match, re = /([^]*?)@([^@\n]*)(?:\n|$)/g;
while (match = re.exec(stack))
- lines.push(match[1].replace(/\n/g, "\\n").substr(0, 80) + match[2]);
+ lines.push(match[1].replace(/\n/g, "\\n").substr(0, 80) + "@" +
+ match[2].replace(/.* -> /, ""));
return lines;
},
@@ -653,6 +661,28 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
return strNum[0] + " " + unitVal[unitIndex];
},
+ /**
+ * Converts *seconds* into a human readable time string.
+ *
+ * @param {number} seconds
+ * @returns {string}
+ */
+ formatSeconds: function formatSeconds(seconds) {
+ function div(num, denom) [Math.round(num / denom), Math.round(num % denom)];
+ let days, hours, minutes;
+
+ [minutes, seconds] = div(seconds, 60);
+ [hours, minutes] = div(minutes, 60);
+ [days, hours] = div(hours, 24);
+ if (days)
+ return days + " days " + hours + " hours"
+ if (hours)
+ return hours + "h " + minutes + "m";
+ if (minutes)
+ return minutes + ":" + seconds;
+ return seconds + "s";
+ },
+
/**
* Returns the file which backs a given URL, if available.
*
diff --git a/pentadactyl/NEWS b/pentadactyl/NEWS
index 4fecca2d..94862607 100644
--- a/pentadactyl/NEWS
+++ b/pentadactyl/NEWS
@@ -64,6 +64,8 @@
consistent interactive help facility (improvements include
listing keys for modes other than Normal, filtering the output
and linking to source locations).
+ - :downloads now opens a download list in the multi-line output
+ buffer.
- Added :cookies command.
* :extadd now supports remote URLs as well as local files on Firefox 4.
* Added :if/:elseif/:else/:endif conditionals.