1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-20 06:07:59 +01:00

Add an experimental prototype download manager replacement. Fix some bugs.

--HG--
extra : rebase_source : aea703414d4bd601bfdea779c5878a30d5b3d782
This commit is contained in:
Kris Maglione
2011-01-16 22:38:44 -05:00
parent b52e78b92e
commit ed696fe5c4
16 changed files with 444 additions and 77 deletions

View File

@@ -133,7 +133,7 @@ var Browser = Module("browser", {
}, },
commands: function () { commands: function () {
commands.add(["downl[oads]", "dl"], commands.add(["old-downl[oads]", "old-dl"],
"Show progress of current downloads", "Show progress of current downloads",
function () { function () {
dactyl.open("chrome://mozapps/content/downloads/downloads.xul", dactyl.open("chrome://mozapps/content/downloads/downloads.xul",

View File

@@ -786,6 +786,10 @@ var Buffer = Module("buffer", {
var persist = services.Persist(); var persist = services.Persist();
persist.persistFlags = persist.PERSIST_FLAGS_FROM_CACHE persist.persistFlags = persist.PERSIST_FLAGS_FROM_CACHE
| persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES; | 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); persist.saveURI(uri, null, null, null, null, file);
}, { }, {
autocomplete: true, autocomplete: true,

View File

@@ -265,10 +265,12 @@ var CommandWidgets = Class("CommandWidgets", {
while (elem.contentDocument.documentURI != elem.getAttribute("src") || while (elem.contentDocument.documentURI != elem.getAttribute("src") ||
["viewable", "complete"].indexOf(elem.contentDocument.readyState) < 0) ["viewable", "complete"].indexOf(elem.contentDocument.readyState) < 0)
util.threadYield(); 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"), get completionList() this._whenReady("completionList", "dactyl-completions"),
@@ -748,8 +750,15 @@ var CommandLine = Module("commandline", {
XML.ignoreWhitespace = false; XML.ignoreWhitespace = false;
XML.prettyPrinting = false; XML.prettyPrinting = false;
let style = typeof str === "string" ? "pre" : "nowrap"; let style = typeof str === "string" ? "pre" : "nowrap";
if (callable(str)) {
this._lastMowOutput = null;
var output = util.xmlToDom(<div class="ex-command-output" style="white-space: nowrap" highlight={highlightGroup}/>, doc);
output.appendChild(str(doc));
}
else {
this._lastMowOutput = <div class="ex-command-output" style={"white-space: " + style} highlight={highlightGroup}>{str}</div>; this._lastMowOutput = <div class="ex-command-output" style={"white-space: " + style} highlight={highlightGroup}>{str}</div>;
let output = util.xmlToDom(this._lastMowOutput, doc); var output = util.xmlToDom(this._lastMowOutput, doc);
}
// FIXME: need to make sure an open MOW is closed when commands // FIXME: need to make sure an open MOW is closed when commands
// that don't generate output are executed // 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 single = flags & (this.FORCE_SINGLELINE | this.DISALLOW_MULTILINE);
let action = this._echoLine; 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; action = this._echoMultiline;
if (single) if (single)
@@ -1073,6 +1082,7 @@ var CommandLine = Module("commandline", {
// FIXME: if 'more' is set and the MOW is not scrollable we should still // FIXME: if 'more' is set and the MOW is not scrollable we should still
// allow a down motion after an up rather than closing // allow a down motion after an up rather than closing
onMultilineOutputEvent: function onMultilineOutputEvent(event) { onMultilineOutputEvent: function onMultilineOutputEvent(event) {
try {
const KILL = false, PASS = true; const KILL = false, PASS = true;
let win = this.widgets.multilineOutput.contentWindow; let win = this.widgets.multilineOutput.contentWindow;
@@ -1080,15 +1090,16 @@ var CommandLine = Module("commandline", {
let key = events.toString(event); let key = events.toString(event);
function openLink(where) { const openLink = function openLink(where) {
event.preventDefault(); event.preventDefault();
dactyl.open(event.target.href, where); dactyl.open(event.target.href, where);
} }
// TODO: Wouldn't multiple handlers be cleaner? --djk // TODO: Wouldn't multiple handlers be cleaner? --djk
if (event.type == "click" && event.target instanceof HTMLAnchorElement) { if (event.type == "click" && (event.target instanceof HTMLAnchorElement ||
event.originalTarget.hasAttributeNS(NS, "command"))) {
let command = event.originalTarget.getAttributeNS(NS.uri, "command"); let command = event.originalTarget.getAttributeNS(NS, "command");
if (command && dactyl.commands[command]) { if (command && dactyl.commands[command]) {
event.preventDefault(); event.preventDefault();
return dactyl.withSavedValues(["forceNewTab"], function () { return dactyl.withSavedValues(["forceNewTab"], function () {
@@ -1122,7 +1133,7 @@ var CommandLine = Module("commandline", {
if (event instanceof MouseEvent) if (event instanceof MouseEvent)
return KILL; return KILL;
function atEnd(dir) !Buffer.isScrollable(elem, dir || 1); const atEnd = function atEnd(dir) !Buffer.isScrollable(elem, dir || 1);
if (!options["more"] || atEnd(1)) { if (!options["more"] || atEnd(1)) {
modes.pop(); modes.pop();
@@ -1130,6 +1141,10 @@ var CommandLine = Module("commandline", {
} }
else else
commandline.updateMorePrompt(false, true); commandline.updateMorePrompt(false, true);
}
catch (e) {
util.reportError(e);
}
return PASS; return PASS;
}, },

View File

@@ -53,16 +53,18 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
}, },
observers: { observers: {
"dactyl-cleanup": function () { "dactyl-cleanup": function dactyl_cleanup() {
let modules = dactyl.modules; let modules = dactyl.modules;
for (let name in values(Object.getOwnPropertyNames(modules).reverse())) { for (let name in values(Object.getOwnPropertyNames(modules).reverse())) {
let mod = Object.getOwnPropertyDescriptor(modules, name).value; let mod = Object.getOwnPropertyDescriptor(modules, name).value;
if (mod instanceof Class) { if (mod instanceof Class) {
if ("cleanup" in mod) if ("cleanup" in mod)
mod.cleanup(); this.trapErrors(mod.cleanup, mod);
if ("destroy" in 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) { userEval: function (str, context, fileName, lineNumber) {
if (jsmodules.__proto__ != window)
str = "with (window) { with (modules) { this.eval(" + str.quote() + ") } }";
if (fileName == null) if (fileName == null)
if (io.sourcing && io.sourcing.file[0] !== "[") if (io.sourcing && io.sourcing.file[0] !== "[")
({ file: fileName, line: lineNumber }) = io.sourcing; ({ file: fileName, line: lineNumber }) = io.sourcing;
@@ -389,9 +394,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
if (!context) if (!context)
context = _userContext; context = _userContext;
if (window.isPrototypeOf(modules))
return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber); 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);
}, },
/** /**
@@ -2178,7 +2181,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
let init = services.environment.get(config.idName + "_INIT"); let init = services.environment.get(config.idName + "_INIT");
let rcFile = io.getRCFile("~"); let rcFile = io.getRCFile("~");
if (dactyl.userEval('typeof document') === "undefined") if (dactyl.userEval("typeof document", null, "test.js") === "undefined")
jsmodules.__proto__ = XPCSafeJSObjectWrapper(window); jsmodules.__proto__ = XPCSafeJSObjectWrapper(window);
try { try {

View File

@@ -175,6 +175,9 @@ var Modes = Module("modes", {
} }
}); });
}, },
cleanup: function () {
modes.reset();
},
_getModeMessage: function () { _getModeMessage: function () {
// when recording a macro // when recording a macro

View File

@@ -70,9 +70,8 @@
<spec>:downl<oa>oads</oa></spec> <spec>:downl<oa>oads</oa></spec>
<description> <description>
<p> <p>
Show progress of current downloads. Open the standard &dactyl.host; Show progress of current downloads. Here, downloads can
download dialog in a new tab. Here, downloads can be paused, be paused, resumed, and canceled.
resumed, and canceled.
</p> </p>
</description> </description>
</item> </item>

View File

@@ -737,9 +737,9 @@ Class.memoize = function memoize(getter)
configurable: true, configurable: true,
enumerable: true, enumerable: true,
init: function (key) { init: function (key) {
this.get = function replace() ( this.get = function replace() let (obj = this.instance || this) (
Class.replaceProperty(this, key, null), Class.replaceProperty(obj, key, null),
Class.replaceProperty(this, key, getter.call(this, key))) Class.replaceProperty(obj, key, getter.call(this, key)))
} }
}); });
@@ -1196,6 +1196,9 @@ update(iter, {
return undefined; return undefined;
}, },
sort: function sort(iter, fn, self)
array(this.toArray(iter).sort(fn, self)),
uniq: function uniq(iter) { uniq: function uniq(iter) {
let seen = {}; let seen = {};
for (let item in iter) for (let item in iter)

View File

@@ -426,9 +426,16 @@ var ConfigBase = Class("ConfigBase", {
Keyword color: red; Keyword color: red;
Tag color: blue; Tag color: blue;
Usage position: relative; padding-right: 2em; Link 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; Link:not(:hover)>LinkInfo opacity: 0; left: 0; width: 1px; height: 1px; overflow: hidden;
Usage:not(:hover)>LineInfo 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 { StatusLine;;;FontFixed {
-moz-appearance: none !important; -moz-appearance: none !important;
@@ -490,6 +497,30 @@ var ConfigBase = Class("ConfigBase", {
HintActive;;* background-color: #88FF00 !important; color: black !important; HintActive;;* background-color: #88FF00 !important; color: black !important;
HintImage;;* opacity: .5 !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
// </css> // </css>
]]></>, /&#x0d;/g, "\n")), ]]></>, /&#x0d;/g, "\n")),

View File

@@ -0,0 +1,268 @@
// Copyright (c) 2011 by Kris Maglione <maglione.k@gmail.com>
//
// 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(
<li highlight="Download" key="row" xmlns:dactyl={NS} xmlns={XHTML}>
<span highlight="DownloadTitle">
<span highlight="Link">
<a key="title" href={self.target.spec}>{self.displayName}</a>
<span highlight="LinkInfo">{self.targetFile.path}</span>
</span>
</span>
<span highlight="DownloadState" key="state"/>
<span highlight="DownloadButtons">
<span highlight="Button" key="pause">Pause</span>
<span highlight="Button" key="remove">Remove</span>
<span highlight="Button" key="resume">Resume</span>
<span highlight="Button" key="retry">Retry</span>
<span highlight="Button" key="cancel">Cancel</span>
<span highlight="Button" key="delete">Delete</span>
</span>
<span highlight="DownloadProgress" key="progress">
<span highlight="DownloadProgressHave" key="progressHave"
/>/<span highlight="DownloadProgressTotal" key="progressTotal"/>
</span>
<span highlight="DownloadPercent" key="percent"/>
<span highlight="DownloadTime" key="time"/>
<a highlight="DownloadSource" key="source" href={self.source.spec}>{self.source.spec}</a>
</li>,
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(<ul highlight="Downloads" key="list" xmlns={XHTML}>
<li highlight="DownloadHead">
<span>Title</span>
<span>Status</span>
<span></span>
<span>Progress</span>
<span></span>
<span>Time remaining</span>
<span>Source</span>
</li>
</ul>, 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:

View File

@@ -92,7 +92,8 @@ var Highlights = Module("Highlight", {
keys: function keys() Object.keys(this.highlight).sort(), 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) { _create: function (agent, args) {
let obj = Highlight.apply(Highlight, args); let obj = Highlight.apply(Highlight, args);
@@ -318,7 +319,7 @@ var Highlights = Module("Highlight", {
if (!modify) if (!modify)
modules.commandline.commandOutput( modules.commandline.commandOutput(
template.tabular(["Key", "Sample", "Link", "CSS"], 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"], "text-align: center"],
([h.class, ([h.class,
<span style={"text-align: center; line-height: 1em;" + h.value + style}>XXX</span>, <span style={"text-align: center; line-height: 1em;" + h.value + style}>XXX</span>,

View File

@@ -149,6 +149,7 @@ var Overlay = Module("Overlay", {
["base", ["base",
"completion", "completion",
"config", "config",
"downloads",
"javascript", "javascript",
"overlay", "overlay",
"prefs", "prefs",

View File

@@ -79,6 +79,7 @@ var Services = Module("Services", {
[Ci.nsIChannel, Ci.nsIInputStreamChannel, Ci.nsIRequest], "setURI"); [Ci.nsIChannel, Ci.nsIInputStreamChannel, Ci.nsIRequest], "setURI");
this.addClass("String", "@mozilla.org/supports-string;1", Ci.nsISupportsString, "data"); 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("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("Timer", "@mozilla.org/timer;1", Ci.nsITimer, "initWithCallback");
this.addClass("StreamCopier", "@mozilla.org/network/async-stream-copier;1",Ci.nsIAsyncStreamCopier, "init"); this.addClass("StreamCopier", "@mozilla.org/network/async-stream-copier;1",Ci.nsIAsyncStreamCopier, "init");
this.addClass("Xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", Ci.nsIXMLHttpRequest); this.addClass("Xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", Ci.nsIXMLHttpRequest);

View File

@@ -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(/,.*/, "")) storage.infoPath = File(modules.IO.runtimePath.replace(/,.*/, ""))
.child("info").child(dactyl.profileName); .child("info").child(dactyl.profileName);
},
cleanup: function (dactyl, modules, window) {
delete window.dactylStorageRefs;
this.removeDeadObservers();
} }
}); });

View File

@@ -331,11 +331,11 @@ var Template = Module("Template", {
this.map(iter, function (item) this.map(iter, function (item)
<tr> <tr>
<td style="padding-right: 2em;"> <td style="padding-right: 2em;">
<span highlight="Usage">{ <span highlight="Usage Link">{
let (name = item.name || item.names[0], frame = item.definedAt) let (name = item.name || item.names[0], frame = item.definedAt)
!frame ? name : !frame ? name :
template.helpLink(help(item), name, "Title") + template.helpLink(help(item), name, "Title") +
<span highlight="LineInfo" xmlns:dactyl={NS}>Defined at {sourceLink(frame)}</span> <span highlight="LinkInfo" xmlns:dactyl={NS}>Defined at {sourceLink(frame)}</span>
}</span> }</span>
</td> </td>
<td>{desc(item)}</td> <td>{desc(item)}</td>

View File

@@ -135,6 +135,13 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
throw FailedAssertion(message, 1); 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 * Returns a RegExp object that matches characters specified in the range
* expression *list*, or signals an appropriate error if *list* is invalid. * 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) { stackLines: function (stack) {
let lines = []; let lines = [];
let match, re = /([^]*?)(@[^@\n]*)(?:\n|$)/g; let match, re = /([^]*?)@([^@\n]*)(?:\n|$)/g;
while (match = re.exec(stack)) 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; return lines;
}, },
@@ -653,6 +661,28 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
return strNum[0] + " " + unitVal[unitIndex]; 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. * Returns the file which backs a given URL, if available.
* *

View File

@@ -64,6 +64,8 @@
consistent interactive help facility (improvements include consistent interactive help facility (improvements include
listing keys for modes other than Normal, filtering the output listing keys for modes other than Normal, filtering the output
and linking to source locations). and linking to source locations).
- :downloads now opens a download list in the multi-line output
buffer.
- Added :cookies command. - Added :cookies command.
* :extadd now supports remote URLs as well as local files on Firefox 4. * :extadd now supports remote URLs as well as local files on Firefox 4.
* Added :if/:elseif/:else/:endif conditionals. * Added :if/:elseif/:else/:endif conditionals.