From 2c74786f1c8473f29b53b96811cfc22350a05bfe Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Mon, 17 Jan 2011 13:28:14 -0500 Subject: [PATCH] Merge some recent fixes into 1.0b5 branch. --- common/content/buffer.js | 9 ++- common/content/commandline.js | 148 ++++++++++++++++++++-------------- common/content/dactyl.js | 19 +++-- common/content/modes.js | 3 + common/modules/base.jsm | 10 ++- common/modules/bootstrap.jsm | 2 - common/modules/config.jsm | 80 +++++++++--------- common/modules/highlight.jsm | 5 +- common/modules/services.jsm | 19 ++--- common/modules/storage.jsm | 8 +- common/modules/template.jsm | 9 ++- common/modules/util.jsm | 34 +++++++- pentadactyl/content/config.js | 4 +- 13 files changed, 212 insertions(+), 138 deletions(-) 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"]],