diff --git a/common/content/buffer.js b/common/content/buffer.js
index fe3adeaf..85de3ecd 100644
--- a/common/content/buffer.js
+++ b/common/content/buffer.js
@@ -174,15 +174,14 @@ var Buffer = Module("buffer", {
else
ext = "";
let re = ext ? RegExp("(\\." + currExt + ")?$") : /$/;
- util.dump(ext.quote(),
- isinstance(node, [Document, HTMLImageElement]),
- node.contentType);
var names = [];
if (node.title)
names.push([node.title, "Page Name"]);
+
if (node.alt)
names.push([node.alt, "Alternate Text"]);
+
if (!isinstance(node, Document) && node.textContent)
names.push([node.textContent, "Link Text"]);
@@ -786,6 +785,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..02187d14 100644
--- a/common/content/commandline.js
+++ b/common/content/commandline.js
@@ -21,6 +21,9 @@ var CommandWidgets = Class("CommandWidgets", {
+
@@ -265,10 +268,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 +753,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 +836,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)
@@ -928,14 +940,21 @@ var CommandLine = Module("commandline", {
},
onContext: function onContext(event) {
- let enabled = {
- link: window.document.popupNode instanceof HTMLAnchorElement,
- selection: !window.document.commandDispatcher.focusedWindow.getSelection().isCollapsed
- };
+ try {
+ let enabled = {
+ link: window.document.popupNode instanceof HTMLAnchorElement,
+ path: window.document.popupNode.hasAttribute("path"),
+ selection: !window.document.commandDispatcher.focusedWindow.getSelection().isCollapsed
+ };
- for (let [, node] in iter(event.target.childNodes)) {
- let group = node.getAttributeNS(NS, "group");
- node.hidden = group && !group.split(/\s+/).some(function (g) enabled[g]);
+ for (let node in array.iterValues(event.target.children)) {
+ let group = node.getAttributeNS(NS, "group");
+ util.dump(node, group, group && !group.split(/\s+/).every(function (g) enabled[g]));
+ node.hidden = group && !group.split(/\s+/).every(function (g) enabled[g]);
+ }
+ }
+ catch (e) {
+ util.reportError(e);
}
return true;
},
@@ -1073,63 +1092,72 @@ 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 && event.button == 2)
+ return PASS;
+
+ 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..e454e1b0 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 || 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);
},
/**
@@ -505,7 +508,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
* @param {string} feature The feature name.
* @returns {boolean}
*/
- has: function (feature) config.features.indexOf(feature) >= 0,
+ has: function (feature) set.has(config.features, feature),
/**
* Returns the URL of the specified help *topic* if it exists.
@@ -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/modules/base.jsm b/common/modules/base.jsm
index 68e875f0..20874c8f 100644
--- a/common/modules/base.jsm
+++ b/common/modules/base.jsm
@@ -489,6 +489,7 @@ function call(fn) {
*/
function memoize(obj, key, getter) {
if (arguments.length == 1) {
+ obj = update({}, obj);
for (let prop in Object.getOwnPropertyNames(obj)) {
let get = objproto.__lookupGetter__.call(obj, prop);
if (get)
@@ -737,9 +738,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 +1197,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/bootstrap.jsm b/common/modules/bootstrap.jsm
index 4149828a..658fbafa 100644
--- a/common/modules/bootstrap.jsm
+++ b/common/modules/bootstrap.jsm
@@ -4,8 +4,6 @@
// given in the LICENSE.txt file included with this file.
"use strict";
-dump(" ======================= bootstrap.jsm " + (typeof JSMLoader) + " ======================= \n");
-
var EXPORTED_SYMBOLS = ["JSMLoader"];
let global = this;
diff --git a/common/modules/config.jsm b/common/modules/config.jsm
index 17378772..0688e8fb 100644
--- a/common/modules/config.jsm
+++ b/common/modules/config.jsm
@@ -36,14 +36,17 @@ var ConfigBase = Class("ConfigBase", {
}
]]>);
+ this.features.push = deprecated("set.add", function push(feature) set.add(this, feature));
if (util.haveGecko("2b"))
- this.features.push("Gecko2");
+ set.add(this.features, "Gecko2");
this.timeout(function () {
services["dactyl:"].pages.dtd = function () [null,
- iter(config.dtdExtra, (["dactyl." + s, config[s]] for each (s in config.dtdStrings)))
- .map(function ([k, v]) [""].join(""))
- .join("\n")]
+ iter(config.dtdExtra,
+ (["dactyl." + k, v] for ([k, v] in iter(config.dtd))),
+ (["dactyl." + s, config[s]] for each (s in config.dtdStrings)))
+ .map(function ([k, v]) [""].join(""))
+ .join("\n")]
});
},
@@ -119,17 +122,20 @@ var ConfigBase = Class("ConfigBase", {
return version;
}),
- // TODO: DTD properties. Cleanup.
- get home() "http://dactyl.sourceforge.net/",
- get apphome() this.home + this.name,
- code: "http://code.google.com/p/dactyl/",
- get issues() this.home + "bug/" + this.name,
- get plugins() "http://dactyl.sf.net/" + this.name + "/plugins",
- get faq() this.home + this.name + "/faq",
- "list.mailto": Class.memoize(function () config.name + "@googlegroups.com"),
- "list.href": Class.memoize(function () "http://groups.google.com/group/" + config.name),
- "hg.latest": Class.memoize(function () config.code + "source/browse/"), // XXX
- "irc": "irc://irc.oftc.net/#pentadactyl",
+ dtd: memoize({
+ get home() "http://dactyl.sourceforge.net/",
+ get apphome() this.home + this.name,
+ code: "http://code.google.com/p/dactyl/",
+ get issues() this.home + "bug/" + this.name,
+ get plugins() "http://dactyl.sf.net/" + this.name + "/plugins",
+ get faq() this.home + this.name + "/faq",
+
+ "list.mailto": Class.memoize(function () config.name + "@googlegroups.com"),
+ "list.href": Class.memoize(function () "http://groups.google.com/group/" + config.name),
+
+ "hg.latest": Class.memoize(function () this.code + "source/browse/"), // XXX
+ "irc": "irc://irc.oftc.net/#pentadactyl",
+ }),
dtdExtra: {
"xmlns.dactyl": "http://vimperator.org/namespaces/liberator",
@@ -142,21 +148,11 @@ var ConfigBase = Class("ConfigBase", {
dtdStrings: [
"appName",
- "apphome",
- "code",
- "faq",
"fileExt",
- "hg.latest",
- "home",
"host",
"hostbin",
"idName",
- "irc",
- "issues",
- "list.href",
- "list.mailto",
"name",
- "plugins",
"version"
],
@@ -208,11 +204,11 @@ var ConfigBase = Class("ConfigBase", {
}),
/**
- * @property {[["string", "string"]]} A sequence of names and descriptions
+ * @property {Object} A mapping of names and descriptions
* of the autocommands available in this application. Primarily used
* for completion results.
*/
- autocommands: [],
+ autocommands: {},
commandContainer: "browser-bottombox",
@@ -262,23 +258,22 @@ var ConfigBase = Class("ConfigBase", {
cleanups: {},
/**
- * @property {[["string", "string", "function"]]} An array of
- * dialogs available via the :dialog command.
- * [0] name - The name of the dialog, used as the first
- * argument to :dialog.
- * [1] description - A description of the dialog, used in
+ * @property {Object} A map of dialogs available via the
+ * :dialog command. Property names map dialog names to an array
+ * as follows:
+ * [0] description - A description of the dialog, used in
* command completion results for :dialog.
- * [2] action - The function executed by :dialog.
+ * [1] action - The function executed by :dialog.
*/
- dialogs: [],
+ dialogs: {},
/**
- * @property {string[]} A list of features available in this
+ * @property {set} A list of features available in this
* application. Used extensively in feature test macros. Use
* dactyl.has(feature) to check for a feature's presence
* in this array.
*/
- features: [],
+ features: {},
/**
* @property {string} The file extension used for command script files.
@@ -426,9 +421,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;
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/services.jsm b/common/modules/services.jsm
index 5e461ed5..74cac15e 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);
@@ -113,10 +114,11 @@ var Services = Module("Services", {
}
["aboutURL", "creator", "description", "developers",
- "homepageURL", "iconURL", "installDate",
- "optionsURL", "releaseNotesURI", "updateDate", "version"].forEach(function (item) {
- addon[item] = getRdfProperty(addon, item);
+ "homepageURL", "installDate", "optionsURL",
+ "releaseNotesURI", "updateDate"].forEach(function (item) {
+ memoize(addon, item, function (item) getRdfProperty(this, item));
});
+
update(addon, {
appDisabled: false,
@@ -147,7 +149,7 @@ var Services = Module("Services", {
for (let [, item] in Iterator(services.extensionManager
.getItemList(Ci.nsIUpdateItem["TYPE_" + type.toUpperCase()], {})))
res.push(this.getAddonByID(item));
- callback(res);
+ return (callback || util.identity)(res);
},
getInstallForFile: function (file, callback, mimetype) {
callback({
@@ -206,14 +208,7 @@ var Services = Module("Services", {
const self = this;
if (name in this && ifaces && !this.__lookupGetter__(name) && !(this[name] instanceof Ci.nsISupports))
throw TypeError();
- this.__defineGetter__(name, function () {
- let res = self._create(class_, ifaces, meth);
- if (!res)
- return null;
-
- delete this[name];
- return this[name] = res;
- });
+ memoize(this, name, function () self._create(class_, ifaces, meth));
},
/**
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..e5ae693b 100644
--- a/common/modules/template.jsm
+++ b/common/modules/template.jsm
@@ -262,12 +262,13 @@ var Template = Module("Template", {
sourceLink: function (frame) {
let url = (frame.filename || "unknown").replace(/.* -> /, "");
+ let path = util.urlPath(url);
XML.ignoreWhitespace = false; XML.prettyPrinting = false;
return {
- util.urlPath(url) + ":" + frame.lineNumber
+ path + ":" + frame.lineNumber
}
},
@@ -331,11 +332,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/content/config.js b/pentadactyl/content/config.js
index fdf8947f..2539ad9f 100644
--- a/pentadactyl/content/config.js
+++ b/pentadactyl/content/config.js
@@ -135,10 +135,10 @@ var Config = Module("config", ConfigBase, {
titlestring: "Pentadactyl"
},
- features: [
+ features: set([
"bookmarks", "hints", "history", "marks", "quickmarks", "sanitizer",
"session", "tabs", "tabs_undo", "windows"
- ],
+ ]),
guioptions: {
m: ["Menubar", ["toolbar-menubar"]],