// Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2008-2012 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.
/** @scope modules */
-default xml namespace = XHTML;
-XML.ignoreWhitespace = false;
-XML.prettyPrinting = false;
-
-var userContext = { __proto__: modules };
-var _userContext = newContext(userContext);
-
var EVAL_ERROR = "__dactyl_eval_error";
var EVAL_RESULT = "__dactyl_eval_result";
var EVAL_STRING = "__dactyl_eval_string";
init: function () {
window.dactyl = this;
// cheap attempt at compatibility
- let prop = { get: deprecated("dactyl", function liberator() dactyl) };
+ let prop = { get: deprecated("dactyl", function liberator() dactyl),
+ configurable: true };
Object.defineProperty(window, "liberator", prop);
Object.defineProperty(modules, "liberator", prop);
this.commands = {};
this._observers = {};
util.addObserver(this);
- this.commands["dactyl.help"] = function (event) {
- let elem = event.originalTarget;
- dactyl.help(elem.getAttribute("tag") || elem.textContent);
- };
this.commands["dactyl.restart"] = function (event) {
dactyl.restart();
};
styles.registerSheet("resource://dactyl-skin/dactyl.css");
+
+ this.cleanups = [];
+ this.cleanups.push(overlay.overlayObject(window, {
+ focusAndSelectUrlBar: function focusAndSelectUrlBar() {
+ switch (options.get("strictfocus").getKey(document.documentURIObject || util.newURI(document.documentURI), "moderate")) {
+ case "laissez-faire":
+ if (!Events.isHidden(window.gURLBar, true))
+ return focusAndSelectUrlBar.superapply(this, arguments);
+ default:
+ // Evil. Ignore.
+ }
+ }
+ }));
},
cleanup: function () {
+ for (let cleanup in values(this.cleanups))
+ cleanup.call(this);
+
delete window.dactyl;
delete window.liberator;
+ // Prevents box ordering bugs after our stylesheet is removed.
+ styles.system.add("cleanup-sheet", config.styleableChrome, literal(/*
+ #TabsToolbar tab { display: none; }
+ */));
styles.unregisterSheet("resource://dactyl-skin/dactyl.css");
+ DOM('#TabsToolbar tab', document).style.display;
},
destroy: function () {
+ this.observe.unregister();
autocommands.trigger("LeavePre", {});
dactyl.triggerObserver("shutdown", null);
util.dump("All dactyl modules destroyed\n");
autocommands.trigger("Leave", {});
},
+ // initially hide all GUI elements, they are later restored unless the user
+ // has :set go= or something similar in his config
+ hideGUI: function () {
+ let guioptions = config.guioptions;
+ for (let option in guioptions) {
+ guioptions[option].forEach(function (elem) {
+ try {
+ document.getElementById(elem).collapsed = true;
+ }
+ catch (e) {}
+ });
+ }
+ },
+
+
observers: {
- "dactyl-cleanup": function dactyl_cleanup() {
+ "dactyl-cleanup": function dactyl_cleanup(subject, reason) {
let modules = dactyl.modules;
for (let mod in values(modules.moduleList.reverse())) {
mod.stale = true;
if ("cleanup" in mod)
- this.trapErrors("cleanup", mod);
+ this.trapErrors("cleanup", mod, reason);
if ("destroy" in mod)
- this.trapErrors("destroy", mod);
+ this.trapErrors("destroy", mod, reason);
}
- for (let mod in values(modules.ownPropertyValues.reverse()))
- if (mod instanceof Class && "INIT" in mod && "cleanup" in mod.INIT)
- this.trapErrors(mod.cleanup, mod, dactyl, modules, window);
+ modules.moduleManager.initDependencies("cleanup");
for (let name in values(Object.getOwnPropertyNames(modules).reverse()))
try {
}
},
- /** @property {string} The name of the current user profile. */
- profileName: Class.memoize(function () {
- // NOTE: services.profile.selectedProfile.name doesn't return
- // what you might expect. It returns the last _actively_ selected
- // profile (i.e. via the Profile Manager or -P option) rather than the
- // current profile. These will differ if the current process was run
- // without explicitly selecting a profile.
-
- let dir = services.directory.get("ProfD", Ci.nsIFile);
- for (let prof in iter(services.profile.profiles))
- if (prof.QueryInterface(Ci.nsIToolkitProfile).rootDir.path === dir.path)
- return prof.name;
- return "unknown";
- }),
+ signals: {
+ "io.source": function ioSource(context, file, modTime) {
+ if (contexts.getDocs(context))
+ help.flush("help/plugins.xml", modTime);
+ }
+ },
+
+ profileName: deprecated("config.profileName", { get: function profileName() config.profileName }),
/**
- * @property {number} The current main mode.
+ * @property {Modes.Mode} The current main mode.
* @see modes#mainModes
*/
mode: deprecated("modes.main", {
set: function mode(val) modes.main = val
}),
- get menuItems() Dactyl.getMenuItems(),
+ getMenuItems: function getMenuItems(targetPath) {
+ function addChildren(node, parent) {
+ DOM(node).createContents();
+
+ if (~["menu", "menupopup"].indexOf(node.localName) && node.children.length)
+ DOM(node).popupshowing({ bubbles: false });
+
+ for (let [, item] in Iterator(node.childNodes)) {
+ if (item.childNodes.length == 0 && item.localName == "menuitem"
+ && !item.hidden
+ && !/rdf:http:/.test(item.getAttribute("label"))) { // FIXME
+ item.dactylPath = parent + item.getAttribute("label");
+ if (!targetPath || targetPath.indexOf(item.dactylPath) == 0)
+ items.push(item);
+ }
+ else {
+ let path = parent;
+ if (item.localName == "menu")
+ path += item.getAttribute("label") + ".";
+ if (!targetPath || targetPath.indexOf(path) == 0)
+ addChildren(item, path);
+ }
+ }
+ }
+
+ let items = [];
+ addChildren(document.getElementById(config.guioptions["m"][1]), "");
+ return items;
+ },
+
+ get menuItems() this.getMenuItems(),
// Global constants
CURRENT_TAB: "here",
NEW_BACKGROUND_TAB: "background-tab",
NEW_WINDOW: "window",
- forceNewTab: false,
- forceNewWindow: false,
+ forceBackground: null,
+ forcePrivate: null,
+ forceTarget: null,
+
+ get forceOpen() ({ background: this.forceBackground,
+ target: this.forceTarget }),
+ set forceOpen(val) {
+ for (let [k, v] in Iterator({ background: "forceBackground", target: "forceTarget" }))
+ if (k in val)
+ this[v] = val[k];
+ },
version: deprecated("config.version", { get: function version() config.version }),
registerObserver: function registerObserver(type, callback, weak) {
if (!(type in this._observers))
this._observers[type] = [];
- this._observers[type].push(weak ? Cu.getWeakReference(callback) : { get: function () callback });
+ this._observers[type].push(weak ? util.weakReference(callback) : { get: function () callback });
},
registerObservers: function registerObservers(obj, prop) {
this._observers[type] = this._observers[type].filter(function (c) c.get() != callback);
},
- // TODO: "zoom": if the zoom value of the current buffer changed
applyTriggerObserver: function triggerObserver(type, args) {
if (type in this._observers)
this._observers[type] = this._observers[type].filter(function (callback) {
},
addUsageCommand: function (params) {
+ function keys(item) (item.names || [item.name]).concat(item.description, item.columns || []);
+
let name = commands.add(params.name, params.description,
function (args) {
let results = array(params.iterate(args))
.sort(function (a, b) String.localeCompare(a.name, b.name));
- let filters = args.map(function (arg) RegExp("\\b" + util.regexp.escape(arg) + "\\b", "i"));
+ let filters = args.map(function (arg) let (re = util.regexp.escape(arg))
+ util.regexp("\\b" + re + "\\b|(?:^|[()\\s])" + re + "(?:$|[()\\s])", "i"));
if (filters.length)
- results = results.filter(function (item) filters.every(function (re) re.test(item.name + " " + item.description)));
+ results = results.filter(function (item) filters.every(function (re) keys(item).some(re.closure.test)));
commandline.commandOutput(
template.usage(results, params.format));
argCount: "*",
completer: function (context, args) {
context.keys.text = util.identity;
- context.keys.description = function () seen[this.text] + " matching items";
+ context.keys.description = function () seen[this.text] + /*L*/" matching items";
+ context.ignoreCase = true;
let seen = {};
- context.completions = array(item.description.toLowerCase().split(/[()\s]+/)
+ context.completions = array(keys(item).join(" ").toLowerCase().split(/[()\s]+/)
for (item in params.iterate(args)))
- .flatten().filter(function (w) /^\w[\w-_']+$/.test(w))
+ .flatten()
.map(function (k) {
seen[k] = (seen[k] || 0) + 1;
return k;
let results = array((params.iterateIndex || params.iterate).call(params, commands.get(name).newArgs()))
.array.sort(function (a, b) String.localeCompare(a.name, b.name));
- let tags = services["dactyl:"].HELP_TAGS;
+ let haveTag = Set.has(help.tags);
for (let obj in values(results)) {
let res = dactyl.generateHelp(obj, null, null, true);
- if (!set.has(tags, obj.helpTag))
- res[1].@tag = obj.helpTag;
+ if (!haveTag(obj.helpTag))
+ res[0][1].tag = obj.helpTag;
yield res;
}
bell: document.getElementById("dactyl-bell"),
strut: document.getElementById("dactyl-bell-strut")
};
- XML.ignoreWhitespace = true;
if (!elems.bell)
- util.overlayWindow(window, {
+ overlay.overlayWindow(window, {
objects: elems,
- prepend: <>
- <window id={document.documentElement.id} xmlns={XUL}>
- <hbox style="display: none" highlight="Bell" id="dactyl-bell" key="bell"/>
- </window>
- </>,
- append: <>
- <window id={document.documentElement.id} xmlns={XUL}>
- <hbox style="display: none" highlight="Bell" id="dactyl-bell-strut" key="strut"/>
- </window>
- </>
+ prepend: [
+ ["window", { id: document.documentElement.id, xmlns: "xul" },
+ ["hbox", { style: "display: none", highlight: "Bell", id: "dactyl-bell", key: "bell" }]]],
+ append: [
+ ["window", { id: document.documentElement.id, xmlns: "xul" },
+ ["hbox", { style: "display: none", highlight: "Bell", id: "dactyl-bell-strut", key: "strut" }]]]
}, elems);
elems.bell.style.height = window.innerHeight + "px";
* This is same as Firefox's readFromClipboard function, but is needed for
* apps like Thunderbird which do not provide it.
*
+ * @param {string} which Which clipboard to write to. Either
+ * "global" or "selection". If not provided, both clipboards are
+ * updated.
+ * @optional
* @returns {string}
*/
- clipboardRead: function clipboardRead(getClipboard) {
+ clipboardRead: function clipboardRead(which) {
try {
- const clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
- const transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
+ const { clipboard } = services;
+ let transferable = services.Transferable();
transferable.addDataFlavor("text/unicode");
- let source = clipboard[getClipboard || !clipboard.supportsSelectionClipboard() ?
+ let source = clipboard[which == "global" || !clipboard.supportsSelectionClipboard() ?
"kGlobalClipboard" : "kSelectionClipboard"];
clipboard.getData(transferable, source);
* Copies a string to the system clipboard. If *verbose* is specified the
* copied string is also echoed to the command line.
*
- * @param {string} str
- * @param {boolean} verbose
+ * @param {string} str The string to write.
+ * @param {boolean} verbose If true, the user is notified of the copied data.
+ * @param {string} which Which clipboard to write to. Either
+ * "global" or "selection". If not provided, both clipboards are
+ * updated.
+ * @optional
*/
- clipboardWrite: function clipboardWrite(str, verbose) {
- const clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
- clipboardHelper.copyString(str);
+ clipboardWrite: function clipboardWrite(str, verbose, which) {
+ if (which == null || which == "selection" && !services.clipboard.supportsSelectionClipboard())
+ services.clipboardHelper.copyString(str);
+ else
+ services.clipboardHelper.copyStringToClipboard(str,
+ services.clipboard["k" + util.capitalize(which) + "Clipboard"]);
if (verbose) {
- let message = { message: "Yanked " + str };
+ let message = { message: _("dactyl.yank", str) };
try {
message.domains = [util.newURI(str).host];
}
echoerr: function echoerr(str, flags) {
flags |= commandline.APPEND_TO_MESSAGES;
- if (isinstance(str, ["Error", "Exception"]))
+ if (isinstance(str, ["DOMException", "Error", "Exception", ErrorBase])
+ || isinstance(str, ["XPCWrappedNative_NoHelper"]) && /^\[Exception/.test(str))
dactyl.reportError(str);
+
if (isObject(str) && "echoerr" in str)
str = str.echoerr;
else if (isinstance(str, ["Error", FailedAssertion]) && str.fileName)
- str = <>{str.fileName.replace(/^.* -> /, "")}: {str.lineNumber}: {str}</>;
+ str = [str.fileName.replace(/^.* -> /, ""), ": ", str.lineNumber, ": ", str].join("");
if (options["errorbells"])
dactyl.beep();
* @param {Object} context The context object into which the script
* should be loaded.
*/
- loadScript: function (uri, context) {
+ loadScript: function loadScript(uri, context) {
JSMLoader.loadSubScript(uri, context, File.defaultEncoding);
},
- userEval: function (str, context, fileName, lineNumber) {
+ userEval: function userEval(str, context, fileName, lineNumber) {
let ctxt;
- if (jsmodules.__proto__ != window)
+ if (jsmodules.__proto__ != window && jsmodules.__proto__ != XPCNativeWrapper(window) &&
+ jsmodules.isPrototypeOf(context))
str = "with (window) { with (modules) { (this.eval || eval)(" + str.quote() + ") } }";
let info = contexts.context;
if (fileName == null)
- if (info && info.file[0] !== "[")
+ if (info)
({ file: fileName, line: lineNumber, context: ctxt }) = info;
- if (!context && fileName && fileName[0] !== "[")
- context = _userContext || ctxt;
+ if (fileName && fileName[0] == "[")
+ fileName = "dactyl://command-line/";
+ else if (!context)
+ context = ctxt || _userContext;
if (isinstance(context, ["Sandbox"]))
return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber);
- else
- try {
- if (!context)
- context = userContext || ctxt;
-
- context[EVAL_ERROR] = null;
- context[EVAL_STRING] = str;
- context[EVAL_RESULT] = null;
- this.loadScript("resource://dactyl-content/eval.js", context);
- if (context[EVAL_ERROR]) {
- try {
- context[EVAL_ERROR].fileName = info.file;
- context[EVAL_ERROR].lineNumber += info.line;
- }
- catch (e) {}
- throw context[EVAL_ERROR];
+
+ if (!context)
+ context = userContext || ctxt;
+
+ if (services.has("dactyl") && services.dactyl.evalInContext)
+ return services.dactyl.evalInContext(str, context, fileName, lineNumber);
+
+ try {
+ context[EVAL_ERROR] = null;
+ context[EVAL_STRING] = str;
+ context[EVAL_RESULT] = null;
+
+ this.loadScript("resource://dactyl-content/eval.js", context);
+ if (context[EVAL_ERROR]) {
+ try {
+ context[EVAL_ERROR].fileName = info.file;
+ context[EVAL_ERROR].lineNumber += info.line;
}
- return context[EVAL_RESULT];
- }
- finally {
- delete context[EVAL_ERROR];
- delete context[EVAL_RESULT];
- delete context[EVAL_STRING];
+ catch (e) {}
+ throw context[EVAL_ERROR];
}
+ return context[EVAL_RESULT];
+ }
+ finally {
+ delete context[EVAL_ERROR];
+ delete context[EVAL_RESULT];
+ delete context[EVAL_STRING];
+ }
},
/**
* Acts like the Function builtin, but the code executes in the
* userContext global.
*/
- userFunc: function () {
+ userFunc: function userFunc() {
return this.userEval(
"(function userFunction(" + Array.slice(arguments, 0, -1).join(", ") + ")" +
" { " + arguments[arguments.length - 1] + " })");
* @param {boolean} silent Whether the command should be echoed on the
* command line.
*/
- execute: function (str, modifiers, silent) {
+ execute: function execute(str, modifiers, silent) {
// skip comments and blank lines
if (/^\s*("|$)/.test(str))
return;
},
focus: function focus(elem, flags) {
- flags = flags || services.focus.FLAG_BYMOUSE;
- try {
- if (elem instanceof Document)
- elem = elem.defaultView;
- if (elem instanceof Element)
- services.focus.setFocus(elem, flags);
- else if (elem instanceof Window)
- services.focus.focusedWindow = elem;
- }
- catch (e) {
- util.dump(elem);
- util.reportError(e);
- }
+ DOM(elem).focus(flags);
},
/**
* @param {string} feature The feature name.
* @returns {boolean}
*/
- has: function (feature) set.has(config.features, feature),
-
- /**
- * Returns the URL of the specified help *topic* if it exists.
- *
- * @param {string} topic The help topic to look up.
- * @param {boolean} consolidated Whether to search the consolidated help page.
- * @returns {string}
- */
- findHelp: function (topic, consolidated) {
- if (!consolidated && topic in services["dactyl:"].FILE_MAP)
- return topic;
- let items = completion._runCompleter("help", topic, null, !!consolidated).items;
- let partialMatch = null;
-
- function format(item) item.description + "#" + encodeURIComponent(item.text);
-
- for (let [i, item] in Iterator(items)) {
- if (item.text == topic)
- return format(item);
- else if (!partialMatch && topic)
- partialMatch = item;
- }
-
- if (partialMatch)
- return format(partialMatch);
- return null;
- },
+ has: function has(feature) Set.has(config.features, feature),
/**
* @private
}
},
+ help: deprecated("help.help", { get: function help() modules.help.closure.help }),
+ findHelp: deprecated("help.findHelp", { get: function findHelp() help.closure.findHelp }),
+
/**
* @private
* Initialize the help system.
*/
- initHelp: function (force) {
- if (force || !this.helpInitialized) {
- if ("noscriptOverlay" in window) {
- noscriptOverlay.safeAllow("chrome-data:", true, false);
- noscriptOverlay.safeAllow("dactyl:", true, false);
- }
-
- // Find help and overlay files with the given name.
- let findHelpFile = function findHelpFile(file) {
- let result = [];
- for (let [, namespace] in Iterator(namespaces)) {
- let url = ["dactyl://", namespace, "/", file, ".xml"].join("");
- let res = util.httpGet(url);
- if (res) {
- if (res.responseXML.documentElement.localName == "document")
- fileMap[file] = url;
- if (res.responseXML.documentElement.localName == "overlay")
- overlayMap[file] = url;
- result.push(res.responseXML);
- }
- }
- return result;
- };
- // Find the tags in the document.
- let addTags = function addTags(file, doc) {
- for (let elem in util.evaluateXPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc))
- for (let tag in values((elem.value || elem.textContent).split(/\s+/)))
- tagMap[tag] = file;
- };
-
- let namespaces = ["locale-local", "locale"];
- services["dactyl:"].init({});
-
- let tagMap = services["dactyl:"].HELP_TAGS;
- let fileMap = services["dactyl:"].FILE_MAP;
- let overlayMap = services["dactyl:"].OVERLAY_MAP;
-
- // Scrape the list of help files from all.xml
- // Manually process main and overlay files, since XSLTProcessor and
- // XMLHttpRequest don't allow access to chrome documents.
- tagMap["all"] = tagMap["all.xml"] = "all";
- tagMap["versions"] = tagMap["versions.xml"] = "versions";
- let files = findHelpFile("all").map(function (doc)
- [f.value for (f in util.evaluateXPath("//dactyl:include/@href", doc))]);
-
- // Scrape the tags from the rest of the help files.
- array.flatten(files).forEach(function (file) {
- tagMap[file + ".xml"] = file;
- findHelpFile(file).forEach(function (doc) {
- addTags(file, doc);
- });
- });
-
- // Process plugin help entries.
- XML.ignoreWhiteSpace = XML.prettyPrinting = false;
-
- let body = XML();
- for (let [, context] in Iterator(plugins.contexts))
- if (context && context.INFO instanceof XML) {
- let info = context.INFO;
- if (info.*.@lang.length()) {
- let lang = config.bestLocale(String(a) for each (a in info.*.@lang));
-
- info.* = info.*.(function::attribute("lang").length() == 0 || @lang == lang);
-
- for each (let elem in info.NS::info)
- for each (let attr in ["@name", "@summary", "@href"])
- if (elem[attr].length())
- info[attr] = elem[attr];
- }
- body += <h2 xmlns={NS.uri} tag={context.INFO.@name + '-plugin'}>{context.INFO.@summary}</h2> +
- context.INFO;
- }
-
- let help =
- '<?xml version="1.0"?>\n' +
- '<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
- '<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
- unescape(encodeURI( // UTF-8 handling hack.
- <document xmlns={NS}
- name="plugins" title={config.appName + " Plugins"}>
- <h1 tag="using-plugins">Using Plugins</h1>
- <toc start="2"/>
-
- {body}
- </document>.toXMLString()));
- fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help];
-
- fileMap["versions"] = function () {
- let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec,
- { mimeType: "text/plain;charset=UTF-8" })
- .responseText;
-
- let re = util.regexp(<![CDATA[
- ^ (?P<comment> \s* # .*\n)
-
- | ^ (?P<space> \s*)
- (?P<char> [-•*+]) \ //
- (?P<content> .*\n
- (?: \2\ \ .*\n | \s*\n)* )
-
- | (?P<par>
- (?: ^ [^\S\n]*
- (?:[^-•*+\s] | [-•*+]\S)
- .*\n
- )+
- )
-
- | (?: ^ [^\S\n]* \n) +
- ]]>, "gmxy");
-
- let betas = util.regexp(/\[(b\d)\]/, "gx");
-
- let beta = array(betas.iterate(NEWS))
- .map(function (m) m[1]).uniq().slice(-1)[0];
-
- default xml namespace = NS;
- function rec(text, level, li) {
- let res = <></>;
- let list, space, i = 0;
-
- for (let match in re.iterate(text)) {
- if (match.comment)
- continue;
- else if (match.char) {
- if (!list)
- res += list = <ul/>;
- let li = <li/>;
- li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li)
- list.* += li;
- }
- else if (match.par) {
- let [, par, tags] = /([^]*?)\s*((?:\[[^\]]+\])*)\n*$/.exec(match.par);
- let t = tags;
- tags = array(betas.iterate(tags)).map(function (m) m[1]);
-
- let group = !tags.length ? "" :
- !tags.some(function (t) t == beta) ? "HelpNewsOld" : "HelpNewsNew";
- if (i === 0 && li) {
- li.@highlight = group;
- group = "";
- }
-
- list = null;
- if (level == 0 && /^.*:\n$/.test(match.par))
- res += <h2>{template.linkifyHelp(par.slice(0, -1), true)}</h2>;
- else {
- let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
- res += <p highlight={group + " HelpNews"}>{
- !tags.length ? "" :
- <hl key="HelpNewsTag">{tags.join(" ")}</hl>
- }{
- a ? <hl key="HelpWarning">{a}</hl> : ""
- }{
- template.linkifyHelp(b, true)
- }</p>;
- }
- }
- i++;
- }
- for each (let attr in res..@highlight) {
- attr.parent().@NS::highlight = attr;
- delete attr.parent().@highlight;
- }
- return res;
- }
-
- let body = rec(NEWS, 0);
- for each (let li in body..li) {
- let list = li..li.(@NS::highlight == "HelpNewsOld");
- if (list.length() && list.length() == li..li.(@NS::highlight != "").length()) {
- for each (let li in list)
- li.@NS::highlight = "";
- li.@NS::highlight = "HelpNewsOld";
- }
- }
-
- XML.prettyPrinting = XML.ignoreWhitespace = false;
- return ["application/xml",
- '<?xml version="1.0"?>\n' +
- '<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
- '<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
- unescape(encodeURI( // UTF-8 handling hack.
- <document xmlns={NS} xmlns:dactyl={NS}
- name="versions" title={config.appName + " Versions"}>
- <h1 tag="versions news NEWS">{config.appName} Versions</h1>
- <toc start="2"/>
-
- {body}
- </document>.toXMLString()))
- ];
- }
- addTags("versions", util.httpGet("dactyl://help/versions").responseXML);
- addTags("plugins", util.httpGet("dactyl://help/plugins").responseXML);
-
- default xml namespace = NS;
-
- overlayMap["index"] = ['text/xml;charset=UTF-8',
- '<?xml version="1.0"?>\n' +
- '<overlay xmlns="' + NS + '">\n' +
- unescape(encodeURI( // UTF-8 handling hack.
- template.map(dactyl.indices, function ([name, iter])
- <dl insertafter={name + "-index"}>{
- template.map(iter(), util.identity)
- }</dl>, <>{"\n\n"}</>))) +
- '\n</overlay>'];
+ initHelp: function initHelp() {
+ if ("noscriptOverlay" in window)
+ window.noscriptOverlay.safeAllow("dactyl:", true, false);
- addTags("index", util.httpGet("dactyl://help-overlay/index").responseXML);
-
- this.helpInitialized = true;
- }
+ help.initialize();
},
- stringifyXML: function (xml) {
- XML.prettyPrinting = false;
- XML.ignoreWhitespace = false;
- return UTF8(xml.toXMLString());
- },
-
- exportHelp: JavaScript.setCompleter(function (path) {
- const FILE = io.File(path);
- const PATH = FILE.leafName.replace(/\..*/, "") + "/";
- const TIME = Date.now();
-
- if (!FILE.exists() && (/\/$/.test(path) && !/\./.test(FILE.leafName)))
- FILE.create(FILE.DIRECTORY_TYPE, octal(755));
-
- dactyl.initHelp();
- if (FILE.isDirectory()) {
- var addDataEntry = function addDataEntry(file, data) FILE.child(file).write(data);
- var addURIEntry = function addURIEntry(file, uri) addDataEntry(file, util.httpGet(uri).responseText);
- }
- else {
- var zip = services.ZipWriter();
- zip.open(FILE, File.MODE_CREATE | File.MODE_WRONLY | File.MODE_TRUNCATE);
-
- addURIEntry = function addURIEntry(file, uri)
- zip.addEntryChannel(PATH + file, TIME, 9,
- services.io.newChannel(uri, null, null), false);
- addDataEntry = function addDataEntry(file, data) // Unideal to an extreme.
- addURIEntry(file, "data:text/plain;charset=UTF-8," + encodeURI(data));
- }
-
- let empty = set("area base basefont br col frame hr img input isindex link meta param"
- .split(" "));
- function fix(node) {
- switch(node.nodeType) {
- case Node.ELEMENT_NODE:
- if (isinstance(node, [HTMLBaseElement]))
- return;
-
- data.push("<"); data.push(node.localName);
- if (node instanceof HTMLHtmlElement)
- data.push(" xmlns=" + XHTML.uri.quote());
-
- for (let { name, value } in array.iterValues(node.attributes)) {
- if (name == "dactyl:highlight") {
- set.add(styles, value);
- name = "class";
- value = "hl-" + value;
- }
- if (name == "href") {
- value = node.href;
- if (value.indexOf("dactyl://help-tag/") == 0) {
- let uri = services.io.newChannel(value, null, null).originalURI;
- value = uri.spec == value ? "javascript:;" : uri.path.substr(1);
- }
- if (!/^#|[\/](#|$)|^[a-z]+:/.test(value))
- value = value.replace(/(#|$)/, ".xhtml$1");
- }
- if (name == "src" && value.indexOf(":") > 0) {
- chromeFiles[value] = value.replace(/.*\//, "");
- value = value.replace(/.*\//, "");
- }
- data.push(" ");
- data.push(name);
- data.push('="');
- data.push(<>{value}</>.toXMLString());
- data.push('"');
- }
- if (node.localName in empty)
- data.push(" />");
- else {
- data.push(">");
- if (node instanceof HTMLHeadElement)
- data.push(<link rel="stylesheet" type="text/css" href="help.css"/>.toXMLString());
- Array.map(node.childNodes, fix);
- data.push("</"); data.push(node.localName); data.push(">");
- }
- break;
- case Node.TEXT_NODE:
- data.push(<>{node.textContent}</>.toXMLString());
- }
- }
-
- let chromeFiles = {};
- let styles = {};
- for (let [file, ] in Iterator(services["dactyl:"].FILE_MAP)) {
- dactyl.open("dactyl://help/" + file);
- dactyl.modules.events.waitForPageLoad();
- let data = [
- '<?xml version="1.0" encoding="UTF-8"?>\n',
- '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n',
- ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
- ];
- fix(content.document.documentElement);
- addDataEntry(file + ".xhtml", data.join(""));
- }
-
- let data = [h for (h in highlight) if (set.has(styles, h.class) || /^Help/.test(h.class))]
- .map(function (h) h.selector
- .replace(/^\[.*?=(.*?)\]/, ".hl-$1")
- .replace(/html\|/g, "") + "\t" + "{" + h.cssText + "}")
- .join("\n");
- addDataEntry("help.css", data.replace(/chrome:[^ ")]+\//g, ""));
-
- addDataEntry("tag-map.json", JSON.stringify(services["dactyl:"].HELP_TAGS));
-
- let m, re = /(chrome:[^ ");]+\/)([^ ");]+)/g;
- while ((m = re.exec(data)))
- chromeFiles[m[0]] = m[2];
-
- for (let [uri, leaf] in Iterator(chromeFiles))
- addURIEntry(leaf, uri);
-
- if (zip)
- zip.close();
- }, [function (context, args) completion.file(context)]),
-
/**
* Generates a help entry and returns it as a string.
*
* @returns {string}
*/
generateHelp: function generateHelp(obj, extraHelp, str, specOnly) {
- default xml namespace = "";
-
let link, tag, spec;
link = tag = spec = util.identity;
let args = null;
if (obj instanceof Command) {
- link = function (cmd) <ex>{cmd}</ex>;
+ link = function (cmd) ["ex", {}, cmd];
args = obj.parseArgs("", CompletionContext(str || ""));
- spec = function (cmd) cmd + (obj.bang ? <oa>!</oa> : <></>);
+ tag = function (cmd) DOM.DOMString(":" + cmd);
+ spec = function (cmd) [
+ obj.count ? ["oa", {}, "count"] : [],
+ cmd,
+ obj.bang ? ["oa", {}, "!"] : []
+ ];
}
else if (obj instanceof Map) {
- spec = function (map) obj.count ? <><oa>count</oa>{map}</> : <>{map}</>;
+ spec = function (map) obj.count ? [["oa", {}, "count"], map] : DOM.DOMString(map);
+ tag = function (map) [
+ let (c = obj.modes[0].char) c ? c + "_" : "",
+ map
+ ]
link = function (map) {
let [, mode, name, extra] = /^(?:(.)_)?(?:<([^>]+)>)?(.*)$/.exec(map);
- let k = <k>{extra}</k>;
+ let k = ["k", {}, extra];
if (name)
- k.@name = name;
+ k[1].name = name;
if (mode)
- k.@mode = mode;
+ k[1].mode = mode;
return k;
};
}
else if (obj instanceof Option) {
- link = function (opt, name) <o>{name}</o>;
+ spec = function () template.map(obj.names, tag, " ");
+ tag = function (name) DOM.DOMString("'" + name + "'");
+ link = function (opt, name) ["o", {}, name];
+ args = { value: "", values: [] };
}
- XML.prettyPrinting = false;
- XML.ignoreWhitespace = false;
- default xml namespace = NS;
-
- // E4X has its warts.
- let br = <>
- </>;
-
- let res = <res>
- <dt>{link(obj.helpTag || obj.name, obj.name)}</dt> <dd>{
- template.linkifyHelp(obj.description ? obj.description.replace(/\.$/, "") : "", true)
- }</dd></res>;
+ let res = [
+ ["dt", {}, link(obj.helpTag || tag(obj.name), obj.name)],
+ ["dd", {},
+ template.linkifyHelp(obj.description ? obj.description.replace(/\.$/, "") : "", true)]];
if (specOnly)
- return res.elements();
-
- res.* += <>
- <item>
- <tags>{template.map(obj.names.slice().reverse(), tag, " ")}</tags>
- <spec>{
- spec(template.highlightRegexp((obj.specs || obj.names)[0],
- /\[(.*?)\]/g,
- function (m, n0) <oa>{n0}</oa>))
- }</spec>{
- !obj.type ? "" : <>
- <type>{obj.type}</type>
- <default>{obj.stringDefaultValue}</default></>}
- <description>{
- obj.description ? br + <p>{template.linkifyHelp(obj.description.replace(/\.?$/, "."), true)}</p> : "" }{
- extraHelp ? br + extraHelp : "" }{
- !(extraHelp || obj.description) ? br + <p>Sorry, no help available.</p> : "" }
- </description>
- </item></>;
+ return res;
+
+ let description = ["description", {},
+ obj.description ? ["p", {}, template.linkifyHelp(obj.description.replace(/\.?$/, "."), true)] : "",
+ extraHelp ? extraHelp : "",
+ !(extraHelp || obj.description) ? ["p", {}, /*L*/ "Sorry, no help available."] : ""]
+
+ res.push(
+ ["item", {},
+ ["tags", {}, template.map(obj.names.slice().reverse(),
+ tag,
+ " ").join("")],
+ ["spec", {},
+ let (name = (obj.specs || obj.names)[0])
+ spec(template.highlightRegexp(tag(name),
+ /\[(.*?)\]/g,
+ function (m, n0) ["oa", {}, n0]),
+ name)],
+ !obj.type ? "" : [
+ ["type", {}, obj.type],
+ ["default", {}, obj.stringDefaultValue]],
+ description]);
function add(ary) {
- res.item.description.* += br +
- let (br = br + <> </>)
- <><dl>{ br + template.map(ary, function ([a, b]) <><dt>{a}</dt> <dd>{b}</dd></>, br) }
- </dl>
- </>;
+ description.push(
+ ["dl", {}, template.map(ary,
+ function ([a, b]) [["dt", {}, a], " ",
+ ["dd", {}, b]])]);
}
- if (obj.completer)
- add(completion._runCompleter(obj.completer, "", null, args).items
+ if (obj.completer && false)
+ add(completion._runCompleter(obj.closure.completer, "", null, args).items
.map(function (i) [i.text, i.description]));
- if (obj.options && obj.options.some(function (o) o.description))
+ if (obj.options && obj.options.some(function (o) o.description) && false)
add(obj.options.filter(function (o) o.description)
.map(function (o) [
o.names[0],
- <>{o.description}{
- o.names.length == 1 ? "" :
- <> (short name: {
- template.map(o.names.slice(1), function (n) <em>{n}</em>, <>, </>)
- })</>
- }</>
+ [o.description,
+ o.names.length == 1 ? "" :
+ ["", " (short name: ",
+ template.map(o.names.slice(1), function (n) ["em", {}, n], ", "),
+ ")"]]
]));
- return res.*.toXMLString()
- .replace(' xmlns="' + NS + '"', "", "g")
- .replace(/^ {12}|[ \t]+$/gm, "")
- .replace(/^\s*\n|\n\s*$/g, "") + "\n";
- },
-
- /**
- * Opens the help page containing the specified *topic* if it exists.
- *
- * @param {string} topic The help topic to open.
- * @param {boolean} consolidated Whether to use the consolidated help page.
- */
- help: function (topic, consolidated) {
- dactyl.initHelp();
- if (!topic) {
- let helpFile = consolidated ? "all" : options["helpfile"];
-
- if (helpFile in services["dactyl:"].FILE_MAP)
- dactyl.open("dactyl://help/" + helpFile, { from: "help" });
- else
- dactyl.echomsg(_("help.noFile", helpFile.quote()));
- return;
- }
-
- let page = this.findHelp(topic, consolidated);
- dactyl.assert(page != null, _("help.noTopic", topic));
- dactyl.open("dactyl://help/" + page, { from: "help" });
+ return DOM.toPrettyXML(res, true, null, { "": String(NS) });
},
/**
* These are set and accessed with the "g:" prefix.
*/
_globalVariables: {},
- globalVariables: deprecated("the options system", {
+ globalVariables: deprecated(_("deprecated.for.theOptionsSystem"), {
get: function globalVariables() this._globalVariables
}),
- loadPlugins: function (args, force) {
+ loadPlugins: function loadPlugins(args, force) {
function sourceDirectory(dir) {
dactyl.assert(dir.isReadable(), _("io.notReadable", dir.path));
let loadplugins = options.get("loadplugins");
if (args)
- loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) }
+ loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) };
dir.readDirectory(true).forEach(function (file) {
- if (file.isFile() && loadplugins.getKey(file.path) && !(!force && file.path in dactyl.pluginFiles)) {
+ if (file.leafName[0] == ".")
+ ;
+ else if (file.isFile() && loadplugins.getKey(file.path)
+ && !(!force && file.path in dactyl.pluginFiles && dactyl.pluginFiles[file.path] >= file.lastModifiedTime)) {
try {
io.source(file.path);
- dactyl.pluginFiles[file.path] = true;
+ dactyl.pluginFiles[file.path] = file.lastModifiedTime;
}
catch (e) {
dactyl.reportError(e);
* @param {string|Object} msg The message to print.
* @param {number} level The logging level 0 - 15.
*/
- log: function (msg, level) {
- let verbose = localPrefs.get("loglevel", 0);
+ log: function log(msg, level) {
+ let verbose = config.prefs.get("loglevel", 0);
if (!level || level <= verbose) {
if (isObject(msg) && !isinstance(msg, _))
}
},
- onClick: function onClick(event) {
- if (event.originalTarget instanceof Element) {
- let command = event.originalTarget.getAttributeNS(NS, "command");
- if (command && event.button == 0) {
- event.preventDefault();
+ events: {
+ click: function onClick(event) {
+ let elem = event.originalTarget;
- if (dactyl.commands[command])
- dactyl.withSavedValues(["forceNewTab"], function () {
- dactyl.forceNewTab = event.ctrlKey || event.shiftKey || event.button == 1;
- dactyl.commands[command](event);
- });
+ if (elem instanceof Element && services.security.isSystemPrincipal(elem.nodePrincipal)) {
+ let command = elem.getAttributeNS(NS, "command");
+ if (command && event.button == 0) {
+ event.preventDefault();
+
+ if (dactyl.commands[command])
+ dactyl.withSavedValues(["forceTarget"], function () {
+ if (event.ctrlKey || event.shiftKey || event.button == 1)
+ dactyl.forceTarget = dactyl.NEW_TAB;
+ dactyl.commands[command](event);
+ });
+ }
}
- }
- },
+ },
- onExecute: function onExecute(event) {
- let cmd = event.originalTarget.getAttribute("dactyl-execute");
- commands.execute(cmd, null, false, null,
- { file: "[Command Line]", line: 1 });
+ "dactyl.execute": function onExecute(event) {
+ let cmd = event.originalTarget.getAttribute("dactyl-execute");
+ commands.execute(cmd, null, false, null,
+ { file: /*L*/"[Command Line]", line: 1 });
+ }
},
/**
* Opens one or more URLs. Returns true when load was initiated, or
* false on error.
*
- * @param {string|Array} urls A representation of the URLs to open. May be
- * either a string, which will be passed to
- * {@see Dactyl#parseURLs}, or an array in the same format as
- * would be returned by the same.
+ * @param {string|Array} urls A representation of the URLs to open.
+ * A string will be passed to {@link Dactyl#parseURLs}. An array may
+ * contain elements of the following forms:
+ *
+ * • {string} A URL to open.
+ * • {[string, {string|Array}]} Pair of a URL and POST data.
+ * • {object} Object compatible with those returned
+ * by {@link DOM#formData}.
+ *
* @param {object} params A set of parameters specifying how to open the
* URLs. The following properties are recognized:
*
* tabs.
* @returns {boolean}
*/
- open: function (urls, params, force) {
+ open: function open(urls, params, force) {
if (typeof urls == "string")
urls = dactyl.parseURLs(urls);
if (urls.length > prefs.get("browser.tabs.maxOpenBeforeWarn", 20) && !force)
- return commandline.input("This will open " + urls.length + " new tabs. Would you like to continue? (yes/[no]) ",
+ return commandline.input(_("dactyl.prompt.openMany", urls.length) + " ",
function (resp) {
if (resp && resp.match(/^y(es)?$/i))
dactyl.open(urls, params, true);
flags |= params[opt] && Ci.nsIWebNavigation["LOAD_FLAGS_" + flag];
let where = params.where || dactyl.CURRENT_TAB;
- let background = ("background" in params) ? params.background
- : params.where == dactyl.NEW_BACKGROUND_TAB;
+ let background = dactyl.forceBackground != null ? dactyl.forceBackground :
+ ("background" in params) ? params.background
+ : params.where == dactyl.NEW_BACKGROUND_TAB;
if (params.from && dactyl.has("tabs")) {
if (!params.where && options.get("newtab").has(params.from))
return;
let browser = config.tabbrowser;
- function open(urls, where) {
+ function open(loc, where) {
try {
- let url = Array.concat(urls)[0];
- let postdata = Array.concat(urls)[1];
+ if (isArray(loc))
+ loc = { url: loc[0], postData: loc[1] };
+ else if (isString(loc))
+ loc = { url: loc };
+ else
+ loc = Object.create(loc);
+
+ if (isString(loc.postData))
+ loc.postData = ["application/x-www-form-urlencoded", loc.postData];
+
+ if (isArray(loc.postData)) {
+ let stream = services.MIMEStream(services.StringStream(loc.postData[1]));
+ stream.addHeader("Content-Type", loc.postData[0]);
+ stream.addContentLength = true;
+ loc.postData = stream;
+ }
// decide where to load the first url
switch (where) {
case dactyl.NEW_TAB:
if (!dactyl.has("tabs"))
- return open(urls, dactyl.NEW_WINDOW);
+ return open(loc, dactyl.NEW_WINDOW);
return prefs.withContext(function () {
prefs.set("browser.tabs.loadInBackground", true);
- return browser.loadOneTab(url, null, null, postdata, background).linkedBrowser.contentDocument;
+ return browser.loadOneTab(loc.url, null, null, loc.postData, background).linkedBrowser.contentDocument;
});
case dactyl.NEW_WINDOW:
- let win = window.openDialog(document.documentURI, "_blank", "chrome,all,dialog=no");
+ let options = ["chrome", "all", "dialog=no"];
+ if (dactyl.forcePrivate)
+ options.push("private");
+
+ let win = window.openDialog(document.documentURI, "_blank", options.join(","));
util.waitFor(function () win.document.readyState === "complete");
browser = win.dactyl && win.dactyl.modules.config.tabbrowser || win.getBrowser();
// FALLTHROUGH
case dactyl.CURRENT_TAB:
- browser.loadURIWithFlags(url, flags, null, null, postdata);
+ browser.loadURIWithFlags(loc.url, flags, null, null, loc.postData);
return browser.contentWindow;
}
}
// any genuine errors go unreported.
}
- if (dactyl.forceNewTab)
- where = dactyl.NEW_TAB;
- else if (dactyl.forceNewWindow)
- where = dactyl.NEW_WINDOW;
+ if (dactyl.forceTarget)
+ where = dactyl.forceTarget;
else if (!where)
where = dactyl.CURRENT_TAB;
* ['www.google.com/search?q=bla', 'www.osnews.com']
*
* @param {string} str
- * @returns {string[]}
+ * @returns {[string]}
*/
parseURLs: function parseURLs(str) {
let urls;
return urls.map(function (url) {
url = url.trim();
- if (/^(\.{0,2}|~)(\/|$)/.test(url)) {
+ if (/^(\.{0,2}|~)(\/|$)/.test(url) || config.OS.isWindows && /^[a-z]:/i.test(url)) {
try {
// Try to find a matching file.
let file = io.File(url);
if (file.exists() && file.isReadable())
- return services.io.newFileURI(file).spec;
+ return file.URI.spec;
}
catch (e) {}
}
// If it starts with a valid protocol, pass it through.
let proto = /^([-\w]+):/.exec(url);
- if (proto && "@mozilla.org/network/protocol;1?name=" + proto[1] in Cc)
- return url.replace(/\s+/g, "");
+ if (proto && services.PROTOCOL + proto[1] in Cc)
+ return url;
// Check for a matching search keyword.
let searchURL = this.has("bookmarks") && bookmarks.getSearchURL(url, false);
}, this);
},
stringToURLArray: deprecated("dactyl.parseURLs", "parseURLs"),
- urlish: Class.memoize(function () util.regexp(<![CDATA[
+ urlish: Class.Memoize(function () util.regexp(literal(/*
^ (
<domain>+ (:\d+)? (/ .*) |
<domain>+ (:\d+) |
<domain>+ \. [a-z0-9]+ |
localhost
) $
- ]]>, "ix", {
- domain: util.regexp(String.replace(<![CDATA[
+ */), "ix", {
+ domain: util.regexp(String.replace(literal(/*
[^
U0000-U002c // U002d-U002e --.
U002f // /
U005b-U0060 // U0061-U007a A-Z
U007b-U007f
]
- ]]>, /U/g, "\\u"), "x")
+ */), /U/g, "\\u"), "x")
})),
pluginFiles: {},
* @param {boolean} force Forcibly quit irrespective of whether all
* windows could be closed individually.
*/
- quit: function (saveSession, force) {
+ quit: function quit(saveSession, force) {
if (!force && !this.confirmQuit())
return;
/**
* Restart the host application.
*/
- restart: function () {
+ restart: function restart(args) {
if (!this.confirmQuit())
return;
+ config.prefs.set("commandline-args", args);
+
services.appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
},
return func.apply(self || this, Array.slice(arguments, 2));
}
catch (e) {
- dactyl.reportError(e, true);
+ try {
+ dactyl.reportError(e, true);
+ }
+ catch (e) {
+ util.reportError(e);
+ }
return e;
}
},
if (error instanceof FailedAssertion && error.noTrace || error.message === "Interrupted") {
let context = contexts.context;
let prefix = context ? context.file + ":" + context.line + ": " : "";
- if (error.message && error.message.indexOf(prefix) !== 0)
+ if (error.message && error.message.indexOf(prefix) !== 0 &&
+ prefix != "[Command Line]:1: ")
error.message = prefix + error.message;
if (error.message)
util.reportError(error);
return;
}
+
if (error.result == Cr.NS_BINDING_ABORTED)
return;
+
if (echo)
dactyl.echoerr(error, commandline.FORCE_SINGLELINE);
else
* @returns {Object}
* @see Commands#parseArgs
*/
- parseCommandLine: function (cmdline) {
+ parseCommandLine: function parseCommandLine(cmdline) {
try {
return commands.get("rehash").parseArgs(cmdline);
}
return [];
}
},
-
- wrapCallback: function (callback, self) {
+ wrapCallback: function wrapCallback(callback, self) {
self = self || this;
- let save = ["forceNewTab", "forceNewWindow"];
+ let save = ["forceOpen"];
let saved = save.map(function (p) dactyl[p]);
return function wrappedCallback() {
let args = arguments;
},
/**
- * @property {Window[]} Returns an array of all the host application's
+ * @property {[Window]} Returns an array of all the host application's
* open windows.
*/
- get windows() [win for (win in iter(services.windowMediator.getEnumerator("navigator:browser")))],
+ get windows() [win for (win in iter(services.windowMediator.getEnumerator("navigator:browser"))) if (win.dactyl)],
}, {
- // initially hide all GUI elements, they are later restored unless the user
- // has :set go= or something similar in his config
- hideGUI: function () {
- let guioptions = config.guioptions;
- for (let option in guioptions) {
- guioptions[option].forEach(function (elem) {
- try {
- document.getElementById(elem).collapsed = true;
- }
- catch (e) {}
- });
- }
- },
+ toolbarHidden: function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true"
+}, {
+ cache: function initCache() {
+ cache.register("help/plugins.xml", function () {
+ // Process plugin help entries.
- // TODO: move this
- getMenuItems: function () {
- function addChildren(node, parent) {
- for (let [, item] in Iterator(node.childNodes)) {
- if (item.childNodes.length == 0 && item.localName == "menuitem"
- && !/rdf:http:/.test(item.getAttribute("label"))) { // FIXME
- item.fullMenuPath = parent + item.getAttribute("label");
- items.push(item);
+ let body = [];
+ for (let [, context] in Iterator(plugins.contexts))
+ try {
+ let info = contexts.getDocs(context);
+ if (DOM.isJSONXML(info)) {
+ let langs = info.slice(2).filter(function (e) isArray(e) && isObject(e[1]) && e[1].lang);
+ if (langs) {
+ let lang = config.bestLocale(l[1].lang for each (l in langs));
+
+ info = info.slice(0, 2).concat(
+ info.slice(2).filter(function (e) !isArray(e) || !isObject(e[1])
+ || e[1].lang == lang));
+
+ for each (let elem in info.slice(2).filter(function (e) isArray(e) && e[0] == "info" && isObject(e[1])))
+ for (let attr in values(["name", "summary", "href"]))
+ if (attr in elem[1])
+ info[attr] = elem[1][attr];
+ }
+ body.push(["h2", { xmlns: "dactyl", tag: info[1].name + '-plugin' },
+ String(info[1].summary)]);
+ body.push(info);
+ }
}
- else {
- let path = parent;
- if (item.localName == "menu")
- path += item.getAttribute("label") + ".";
- addChildren(item, path);
+ catch (e) {
+ util.reportError(e);
}
- }
- }
- let items = [];
- addChildren(document.getElementById(config.guioptions["m"][1]), "");
- return items;
- }
-}, {
- events: function () {
- events.listen(window, "click", dactyl.closure.onClick, true);
- events.listen(window, "dactyl.execute", dactyl.closure.onExecute, true);
+ return '<?xml version="1.0"?>\n' +
+ '<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
+ DOM.toXML(
+ ["document", { xmlns: "dactyl", name: "plugins",
+ title: config.appName + ", Plugins" },
+ ["h1", { tag: "using-plugins" }, _("help.title.Using Plugins")],
+ ["toc", { start: "2" }],
+
+ body]);
+ });
+
+ cache.register("help/index.xml", function () {
+ return '<?xml version="1.0"?>\n' +
+ DOM.toXML(["overlay", { xmlns: "dactyl" },
+ template.map(dactyl.indices, function ([name, iter])
+ ["dl", { insertafter: name + "-index" },
+ template.map(iter(), util.identity)],
+ "\n\n")]);
+ });
+
+ cache.register("help/gui.xml", function () {
+ return '<?xml version="1.0"?>\n' +
+ DOM.toXML(["overlay", { xmlns: "dactyl" },
+ ["dl", { insertafter: "dialog-list" },
+ template.map(config.dialogs, function ([name, val])
+ (!val[2] || val[2]())
+ ? [["dt", {}, name],
+ ["dd", {}, val[0]]]
+ : undefined,
+ "\n")]]);
+ });
+
+ cache.register("help/privacy.xml", function () {
+ return '<?xml version="1.0"?>\n' +
+ DOM.toXML(["overlay", { xmlns: "dactyl" },
+ ["dl", { insertafter: "sanitize-items" },
+ template.map(options.get("sanitizeitems").values
+ .sort(function (a, b) String.localeCompare(a.name,
+ b.name)),
+ function ({ name, description })
+ [["dt", {}, name],
+ ["dd", {}, template.linkifyHelp(description, true)]],
+ "\n")]]);
+ });
+ },
+ events: function initEvents() {
+ events.listen(window, dactyl, "events", true);
},
// Only general options are added here, which are valid for all Dactyl extensions
- options: function () {
+ options: function initOptions() {
options.add(["errorbells", "eb"],
"Ring the bell when an error message is displayed",
"boolean", false);
M: ["Always show messages outside of the status line"]
},
setter: function (opts) {
- if (loaded.commandline)
+ if (loaded.commandline || ~opts.indexOf("c"))
commandline.widgets.updateVisibility();
}
},
true);
prefs.safeSet("layout.scrollbar.side", opts.indexOf("l") >= 0 ? 3 : 2,
- "See 'guioptions' scrollbar flags.");
+ _("option.guioptions.safeSet"));
},
validator: function (opts) Option.validIf(!(opts.indexOf("l") >= 0 && opts.indexOf("r") >= 0),
UTF8("Only one of ‘l’ or ‘r’ allowed"))
options.add(["guioptions", "go"],
"Show or hide certain GUI elements like the menu or toolbar",
- "charlist", config.defaults.guioptions || "", {
+ "charlist", "", {
// FIXME: cleanup
cleanupValue: config.cleanups.guioptions ||
- "r" + [k for ([k, v] in iter(groups[1].opts))
- if (!document.getElementById(v[1][0]).collapsed)].join(""),
+ "rb" + [k for ([k, v] in iter(groups[1].opts))
+ if (!Dactyl.toolbarHidden(document.getElementById(v[1][0])))].join(""),
values: array(groups).map(function (g) [[k, v[0]] for ([k, v] in Iterator(g.opts))]).flatten(),
options.add(["titlestring"],
"The string shown at the end of the window title",
- "string", config.defaults.titlestring || config.host,
+ "string", config.host,
{
setter: function (value) {
let win = document.documentElement;
function updateTitle(old, current) {
- document.title = document.title.replace(RegExp("(.*)" + util.regexp.escape(old)), "$1" + current);
+ if (config.browser.updateTitlebar)
+ config.browser.updateTitlebar();
+ else
+ document.title = document.title.replace(RegExp("(.*)" + util.regexp.escape(old)), "$1" + current);
}
- if (services.has("privateBrowsing")) {
+ if (win.hasAttribute("titlemodifier_privatebrowsing")) {
let oldValue = win.getAttribute("titlemodifier_normal");
let suffix = win.getAttribute("titlemodifier_privatebrowsing").substr(oldValue.length);
win.setAttribute("titlemodifier_normal", value);
win.setAttribute("titlemodifier_privatebrowsing", value + suffix);
- if (services.privateBrowsing.privateBrowsingEnabled) {
+ if (storage.privateMode) {
updateTitle(oldValue + suffix, value + suffix);
+ win.setAttribute("titlemodifier", value + suffix);
return value;
}
}
options.add(["urlseparator", "urlsep", "us"],
"The regular expression used to separate multiple URLs in :open and friends",
- "string", "\\|",
+ "string", " \\| ",
{ validator: function (value) RegExp(value) });
options.add(["verbose", "vbs"],
{
setter: function (value) {
prefs.safeSet("accessibility.typeaheadfind.enablesound", !value,
- "See 'visualbell' option");
+ _("option.safeSet", "visualbell"));
return value;
}
});
},
- mappings: function () {
- mappings.add([modes.MAIN], ["<F1>"],
- "Open the introductory help page",
- function () { dactyl.help(); });
-
- mappings.add([modes.MAIN], ["<A-F1>"],
- "Open the single, consolidated help page",
- function () { ex.helpall(); });
-
+ mappings: function initMappings() {
if (dactyl.has("session"))
mappings.add([modes.NORMAL], ["ZQ"],
"Quit and don't save the session",
function () { dactyl.quit(true); });
},
- commands: function () {
+ commands: function initCommands() {
commands.add(["dia[log]"],
"Open a " + config.appName + " dialog",
function (args) {
}
}, {
argCount: "1",
- bang: true,
completer: function (context) {
context.ignoreCase = true;
completion.dialog(context);
"Execute the specified menu item from the command line",
function (args) {
let arg = args[0] || "";
- let items = Dactyl.getMenuItems();
+ let items = dactyl.getMenuItems(arg);
- dactyl.assert(items.some(function (i) i.fullMenuPath == arg),
+ dactyl.assert(items.some(function (i) i.dactylPath == arg),
_("emenu.notFound", arg));
for (let [, item] in Iterator(items)) {
- if (item.fullMenuPath == arg)
+ if (item.dactylPath == arg) {
+ dactyl.assert(!item.disabled, _("error.disabled", item.dactylPath));
item.doCommand();
+ }
}
}, {
argCount: "1",
literal: 0
});
- [
- {
- name: "h[elp]",
- description: "Open the introductory help page"
- }, {
- name: "helpa[ll]",
- description: "Open the single consolidated help page"
- }
- ].forEach(function (command) {
- let consolidated = command.name == "helpa[ll]";
-
- commands.add([command.name],
- command.description,
- function (args) {
- dactyl.assert(!args.bang, _("help.dontPanic"));
- dactyl.help(args.literalArg, consolidated);
- }, {
- argCount: "?",
- bang: true,
- completer: function (context) completion.help(context, consolidated),
- literal: 0
- });
- });
-
commands.add(["loadplugins", "lpl"],
- "Load all plugins immediately",
+ "Load all or matching plugins",
function (args) {
dactyl.loadPlugins(args.length ? args : null, args.bang);
},
bang: true,
keepQuotes: true,
serialGroup: 10,
- serialize: function () [
+ serialize: function () [
{
command: this.name,
literalArg: options["loadplugins"].join(" ")
literal: 0
});
+ commands.add(["pr[ivate]", "pr0n", "porn"],
+ "Enable privacy features of a command, when applicable, and do not save the invocation in command history",
+ function (args) {
+ dactyl.withSavedValues(["forcePrivate"], function () {
+ this.forcePrivate = true;
+ dactyl.execute(args[0], null, true);
+ });
+ }, {
+ argCount: "1",
+ completer: function (context) completion.ex(context),
+ literal: 0,
+ privateData: "never-save",
+ subCommand: 0
+ });
+
+ commands.add(["exit", "x"],
+ "Quit " + config.appName,
+ function (args) {
+ dactyl.quit(false, args.bang);
+ }, {
+ argCount: "0",
+ bang: true
+ });
+
commands.add(["q[uit]"],
dactyl.has("tabs") ? "Quit current tab" : "Quit application",
function (args) {
bang: true
});
+ let startupOptions = [
+ {
+ names: ["+u"],
+ description: "The initialization file to execute at startup",
+ type: CommandOption.STRING
+ },
+ {
+ names: ["++noplugin"],
+ description: "Do not automatically load plugins"
+ },
+ {
+ names: ["++cmd"],
+ description: "Ex commands to execute prior to initialization",
+ type: CommandOption.STRING,
+ multiple: true
+ },
+ {
+ names: ["+c"],
+ description: "Ex commands to execute after initialization",
+ type: CommandOption.STRING,
+ multiple: true
+ },
+ {
+ names: ["+purgecaches"],
+ description: "Purge " + config.appName + " caches at startup",
+ type: CommandOption.NOARG
+ }
+ ];
+
commands.add(["reh[ash]"],
"Reload the " + config.appName + " add-on",
function (args) {
if (args.trailing)
- JSMLoader.rehashCmd = args.trailing; // Hack.
+ storage.storeForSession("rehashCmd", args.trailing); // Hack.
args.break = true;
- util.rehash(args);
+
+ if (args["+purgecaches"])
+ cache.flush();
+
+ util.delay(function () { util.rehash(args) });
},
{
- argCount: "0",
- options: [
- {
- names: ["+u"],
- description: "The initialization file to execute at startup",
- type: CommandOption.STRING
- },
- {
- names: ["++noplugin"],
- description: "Do not automatically load plugins"
- },
- {
- names: ["++cmd"],
- description: "Ex commands to execute prior to initialization",
- type: CommandOption.STRING,
- multiple: true
- },
- {
- names: ["+c"],
- description: "Ex commands to execute after initialization",
- type: CommandOption.STRING,
- multiple: true
- }
- ]
+ argCount: "0", // FIXME
+ options: startupOptions
});
commands.add(["res[tart]"],
- "Force " + config.appName + " to restart",
- function () { dactyl.restart(); });
+ "Force " + config.host + " to restart",
+ function (args) {
+ if (args["+purgecaches"])
+ cache.flush();
- function findToolbar(name) util.evaluateXPath(
- "//*[@toolbarname=" + util.escapeString(name, "'") + "]",
+ dactyl.restart(args.string);
+ },
+ {
+ argCount: "0",
+ options: startupOptions
+ });
+
+ function findToolbar(name) DOM.XPath(
+ "//*[@toolbarname=" + util.escapeString(name, "'") + " or " +
+ "@toolbarname=" + util.escapeString(name.trim(), "'") + "]",
document).snapshotItem(0);
var toolbox = document.getElementById("navigator-toolbox");
if (toolbox) {
- let hidden = function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true";
-
let toolbarCommand = function (names, desc, action, filter) {
commands.add(names, desc,
function (args) {
action(toolbar);
events.checkFocus();
}, {
- argcount: "1",
+ argCount: "1",
completer: function (context) {
completion.toolbar(context);
if (filter)
toolbarCommand(["toolbars[how]", "tbs[how]"], "Show the named toolbar",
function (toolbar) dactyl.setNodeVisible(toolbar, true),
- function ({ item }) hidden(item));
+ function ({ item }) Dactyl.toolbarHidden(item));
toolbarCommand(["toolbarh[ide]", "tbh[ide]"], "Hide the named toolbar",
function (toolbar) dactyl.setNodeVisible(toolbar, false),
- function ({ item }) !hidden(item));
+ function ({ item }) !Dactyl.toolbarHidden(item));
toolbarCommand(["toolbart[oggle]", "tbt[oggle]"], "Toggle the named toolbar",
- function (toolbar) dactyl.setNodeVisible(toolbar, hidden(toolbar)));
+ function (toolbar) dactyl.setNodeVisible(toolbar, Dactyl.toolbarHidden(toolbar)));
}
commands.add(["time"],
args = args[0] || "";
if (args[0] == ":")
- var method = function () commands.execute(args, null, true);
+ var func = function () commands.execute(args, null, false);
else
- method = dactyl.userFunc(args);
+ func = dactyl.userFunc(args);
try {
if (count > 1) {
for (let i in util.interruptibleRange(0, count, 500)) {
let now = Date.now();
- method();
+ func();
total += Date.now() - now;
}
totalUnits = "msec";
commandline.commandOutput(
- <table>
- <tr highlight="Title" align="left">
- <th colspan="3">Code execution summary</th>
- </tr>
- <tr><td>  Executed:</td><td align="right"><span class="times-executed">{count}</span></td><td>times</td></tr>
- <tr><td>  Average time:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
- <tr><td>  Total time:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
- </table>);
+ ["table", {}
+ ["tr", { highlight: "Title", align: "left" },
+ ["th", { colspan: "3" }, _("title.Code execution summary")]],
+ ["tr", {},
+ ["td", {}, _("title.Executed"), ":"],
+ ["td", { align: "right" },
+ ["span", { class: "times-executed" }, count]],
+ ["td", {}, /*L*/"times"]],
+ ["tr", {},
+ ["td", {}, _("title.Average time"), ":"],
+ ["td", { align: "right" },
+ ["span", { class: "time-average" }, each.toFixed(2)]],
+ ["td", {}, eachUnits]],
+ ["tr", {},
+ ["td", {}, _("title.Total time"), ":"],
+ ["td", { align: "right" },
+ ["span", { class: "time-total" }, total.toFixed(2)]],
+ ["td", {}, totalUnits]]]);
}
else {
let beforeTime = Date.now();
- method();
+ func();
if (special)
return;
dactyl.echoerr(e);
}
}, {
- argCount: "+",
+ argCount: "1",
bang: true,
completer: function (context) {
if (/^:/.test(context.filter))
vbs.setFrom = setFrom;
}
}, {
- argCount: "+",
+ argCount: "1",
completer: function (context) completion.ex(context),
count: true,
literal: 0,
function (args) {
if (args.bang)
dactyl.open("about:");
- else
- commandline.commandOutput(<>
- {config.appName} {config.version} running on:<br/>{navigator.userAgent}
- </>);
+ else {
+ let date = config.buildDate;
+ date = date ? " (" + date + ")" : "";
+
+ commandline.commandOutput([
+ ["div", {}, [config.appName, " ", config.version, date, " running on: "].join("")],
+ ["div", {}, [window.navigator.userAgent].join("")]
+ ])
+ }
}, {
argCount: "0",
bang: true
},
- completion: function () {
+ completion: function initCompletion() {
completion.dialog = function dialog(context) {
context.title = ["Dialog"];
context.filters.push(function ({ item }) !item[2] || item[2]());
context.completions = [[k, v[0], v[2]] for ([k, v] in Iterator(config.dialogs))];
};
- completion.help = function help(context, consolidated) {
- dactyl.initHelp();
- context.title = ["Help"];
- context.anchored = false;
- context.completions = services["dactyl:"].HELP_TAGS;
- if (consolidated)
- context.keys = { text: 0, description: function () "all" };
- };
-
completion.menuItem = function menuItem(context) {
context.title = ["Menu Path", "Label"];
context.anchored = false;
- context.keys = { text: "fullMenuPath", description: function (item) item.getAttribute("label") };
- context.completions = dactyl.menuItems;
+ context.keys = {
+ text: "dactylPath",
+ description: function (item) item.getAttribute("label"),
+ highlight: function (item) item.disabled ? "Disabled" : ""
+ };
+ context.generate = function () dactyl.menuItems;
};
var toolbox = document.getElementById("navigator-toolbox");
completion.toolbar = function toolbar(context) {
context.title = ["Toolbar"];
context.keys = { text: function (item) item.getAttribute("toolbarname"), description: function () "" };
- context.completions = util.evaluateXPath("//*[@toolbarname]", document);
+ context.completions = DOM.XPath("//*[@toolbarname]", document);
};
completion.window = function window(context) {
context.completions = dactyl.windows;
};
},
- load: function () {
+ load: function initLoad() {
dactyl.triggerObserver("load");
dactyl.log(_("dactyl.modulesLoaded"), 3);
+ userContext.DOM = Class("DOM", DOM, { init: function DOM_(sel, ctxt) DOM(sel, ctxt || buffer.focusedFrame.document) });
+ userContext.$ = modules.userContext.DOM;
+
+ // Hack: disable disabling of Personas in private windows.
+ let root = document.documentElement;
+
+ if (PrivateBrowsingUtils && PrivateBrowsingUtils.isWindowPrivate(window)
+ && root._lightweightTheme
+ && root._lightweightTheme._lastScreenWidth == null) {
+
+ dactyl.withSavedValues.call(PrivateBrowsingUtils,
+ ["isWindowPrivate"], function () {
+ PrivateBrowsingUtils.isWindowPrivate = function () false;
+
+ Cu.import("resource://gre/modules/LightweightThemeConsumer.jsm", {})
+ .LightweightThemeConsumer.call(root._lightweightTheme, document);
+ });
+ }
+
dactyl.timeout(function () {
try {
- var args = JSMLoader.commandlineArgs || services.commandLineHandler.optionValue;
+ var args = config.prefs.get("commandline-args")
+ || storage.session.commandlineArgs
+ || services.commandLineHandler.optionValue;
+
+ config.prefs.reset("commandline-args");
+
if (isString(args))
args = dactyl.parseCommandLine(args);
dactyl.log(_("dactyl.commandlineOpts", util.objectToString(dactyl.commandLineOptions)), 3);
- // first time intro message
- const firstTime = "extensions." + config.name + ".firsttime";
- if (prefs.get(firstTime, true)) {
+ if (config.prefs.get("first-run", true))
dactyl.timeout(function () {
- this.withSavedValues(["forceNewTab"], function () {
- this.forceNewTab = true;
- this.help();
- prefs.set(firstTime, false);
+ config.prefs.set("first-run", false);
+ this.withSavedValues(["forceTarget"], function () {
+ this.forceTarget = dactyl.NEW_TAB;
+ help.help();
});
}, 1000);
- }
// TODO: we should have some class where all this guioptions stuff fits well
- // Dactyl.hideGUI();
+ // dactyl.hideGUI();
if (dactyl.userEval("typeof document", null, "test.js") === "undefined")
jsmodules.__proto__ = XPCSafeJSObjectWrapper(window);
if (dactyl.commandLineOptions.rcFile == "NONE" || dactyl.commandLineOptions.noPlugins)
options["loadplugins"] = [];
- if (options["loadplugins"])
+ if (options["loadplugins"].length)
dactyl.loadPlugins();
}
catch (e) {
dactyl.execute(cmd);
});
- if (JSMLoader.rehashCmd)
- dactyl.execute(JSMLoader.rehashCmd);
- JSMLoader.rehashCmd = null;
+ if (storage.session.rehashCmd)
+ dactyl.execute(storage.session.rehashCmd);
+ storage.session.rehashCmd = null;
dactyl.fullyInitialized = true;
dactyl.triggerObserver("enter", null);