//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
-"use strict";
+/* use strict */
/** @scope modules */
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(util.overlayObject(window, {
+ this.cleanups.push(overlay.overlayObject(window, {
focusAndSelectUrlBar: function focusAndSelectUrlBar() {
switch (options.get("strictfocus").getKey(document.documentURIObject || util.newURI(document.documentURI), "moderate")) {
case "laissez-faire":
delete window.dactyl;
delete window.liberator;
+ // Prevents box ordering bugs after our stylesheet is removed.
+ styles.system.add("cleanup-sheet", config.styleableChrome, <![CDATA[
+ #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");
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, reason);
+ 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 (context.INFO)
+ help.flush("help/plugins.xml", modTime);
+ }
+ },
+
+ profileName: deprecated("config.profileName", { get: function profileName() config.profileName }),
/**
* @property {Modes.Mode} The current main mode.
set: function mode(val) modes.main = val
}),
- get menuItems() {
- function dispatch(node, name) {
- let event = node.ownerDocument.createEvent("Events");
- event.initEvent(name, false, false);
- node.dispatchEvent(event);
- }
-
+ getMenuItems: function getMenuItems(targetPath) {
function addChildren(node, parent) {
+ DOM(node).createContents();
+
if (~["menu", "menupopup"].indexOf(node.localName) && node.children.length)
- dispatch(node, "popupshowing");
+ 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");
- items.push(item);
+ if (!targetPath || targetPath.indexOf(item.dactylPath) == 0)
+ items.push(item);
}
else {
let path = parent;
if (item.localName == "menu")
path += item.getAttribute("label") + ".";
- addChildren(item, path);
+ if (!targetPath || targetPath.indexOf(path) == 0)
+ addChildren(item, path);
}
}
}
return items;
},
+ get menuItems() this.getMenuItems(),
+
// Global constants
CURRENT_TAB: "here",
NEW_TAB: "tab",
NEW_BACKGROUND_TAB: "background-tab",
NEW_WINDOW: "window",
- forceNewTab: false,
- forceNewWindow: false,
+ forceBackground: 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) {
let results = array(params.iterate(args))
.sort(function (a, b) String.localeCompare(a.name, b.name));
- let filters = args.map(function (arg) util.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) keys(item).some(re.closure.test)));
completer: function (context, args) {
context.keys.text = util.identity;
context.keys.description = function () seen[this.text] + /*L*/" matching items";
+ context.ignoreCase = true;
let seen = {};
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))
+ if (!haveTag(obj.helpTag))
res[1].@tag = obj.helpTag;
yield res;
};
XML.ignoreWhitespace = true;
if (!elems.bell)
- util.overlayWindow(window, {
+ overlay.overlayWindow(window, {
objects: elems,
prepend: <>
<window id={document.documentElement.id} xmlns={XUL}>
* 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: _("dactyl.yank", str) };
echoerr: function echoerr(str, flags) {
flags |= commandline.APPEND_TO_MESSAGES;
- if (isinstance(str, ["DOMException", "Error", "Exception"]) || isinstance(str, ["XPCWrappedNative_NoHelper"]) && /^\[Exception/.test(str))
+ 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)
userEval: function (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] !== "[")
+ 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];
+ }
},
/**
},
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);
},
/**
*/
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;
- },
-
/**
* @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) {
- // Waits for the add-on to become available, if necessary.
- config.addon;
- config.version;
-
- 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))
- try {
- let info = contexts.getDocs(context);
- if (info instanceof XML) {
- 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={info.@name + '-plugin'}>{info.@summary}</h2> +
- info;
- }
- }
- catch (e) {
- util.reportError(e);
- }
+ initHelp: function initHelp() {
+ if ("noscriptOverlay" in window)
+ noscriptOverlay.safeAllow("dactyl:", true, false);
- 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' +
- <document xmlns={NS}
- name="plugins" title={config.appName + " Plugins"}>
- <h1 tag="using-plugins">{_("help.title.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) {
- XML.ignoreWhitespace = XML.prettyPrinting = false;
-
- 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)) {
- let text = par.slice(0, -1);
- res += <h2 tag={"news-" + text}>{template.linkifyHelp(text, 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;
- }
-
- XML.ignoreWhitespace = XML.prettyPrinting = false;
- 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";
- }
- }
-
- 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' +
- <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}>{
- template.map(dactyl.indices, function ([name, iter])
- <dl insertafter={name + "-index"}>{
- template.map(iter(), util.identity)
- }</dl>, <>{"\n\n"}</>)
- }</overlay>];
- addTags("index", util.httpGet("dactyl://help-overlay/index").responseXML);
-
- overlayMap["gui"] = ['text/xml;charset=UTF-8',
- '<?xml version="1.0"?>\n' +
- <overlay xmlns={NS}>
- <dl insertafter="dialog-list">{
- template.map(config.dialogs, function ([name, val])
- (!val[2] || val[2]())
- ? <><dt>{name}</dt><dd>{val[0]}</dd></>
- : undefined,
- <>{"\n"}</>)
- }</dl>
- </overlay>];
-
-
- this.helpInitialized = true;
- }
+ help.initialize();
},
stringifyXML: function (xml) {
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(),
- " xmlns:dactyl=" + NS.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 || value;
- 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(" ", name, '="',
- <>{value}</>.toXMLString().replace(/"/g, """),
- '"');
- }
- 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("</", node.localName, ">");
- }
- break;
- case Node.TEXT_NODE:
- data.push(<>{node.textContent}</>.toXMLString());
- }
- }
-
- let chromeFiles = {};
- let styles = {};
- for (let [file, ] in Iterator(services["dactyl:"].FILE_MAP)) {
- let url = "dactyl://help/" + file;
- dactyl.open(url);
- util.waitFor(function () content.location.href == url && buffer.loaded
- && content.document.documentElement instanceof HTMLHtmlElement,
- 15000);
- events.waitForPageLoad();
- var 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.
*
if (obj instanceof Command) {
link = function (cmd) <ex>{cmd}</ex>;
args = obj.parseArgs("", CompletionContext(str || ""));
+ tag = function (cmd) <>:{cmd}</>;
spec = function (cmd) <>{
obj.count ? <oa>count</oa> : <></>
}{
}
else if (obj instanceof Map) {
spec = function (map) obj.count ? <><oa>count</oa>{map}</> : <>{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>;
};
}
else if (obj instanceof Option) {
- tag = spec = function (name) <>'{name}'</>;
+ spec = function () template.map(obj.names, tag, " ");
+ tag = function (name) <>'{name}'</>;
link = function (opt, name) <o>{name}</o>;
args = { value: "", values: [] };
}
</>;
let res = <res>
- <dt>{link(obj.helpTag || obj.name, obj.name)}</dt> <dd>{
+ <dt>{link(obj.helpTag || tag(obj.name), obj.name)}</dt> <dd>{
template.linkifyHelp(obj.description ? obj.description.replace(/\.$/, "") : "", true)
}</dd></res>;
if (specOnly)
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>{let (name = (obj.specs || obj.names)[0])
+ spec(template.highlightRegexp(tag(name),
+ /\[(.*?)\]/g,
+ function (m, n0) <oa>{n0}</oa>),
+ name)
}</spec>{
!obj.type ? "" : <>
<type>{obj.type}</type>
.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" });
- },
-
/**
* The map of global variables.
*
loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) };
dir.readDirectory(true).forEach(function (file) {
- if (file.isFile() && loadplugins.getKey(file.path)
+ 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);
* @param {number} level The logging level 0 - 15.
*/
log: function (msg, level) {
- let verbose = localPrefs.get("loglevel", 0);
+ 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: /*L*/"[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
+ * @param {string|object|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.
+ * {@link Dactyl#parseURLs}, an array in the same format as
+ * would be returned by the same, or an object as returned by
+ * {@link DOM#formData}.
* @param {object} params A set of parameters specifying how to open the
* URLs. The following properties are recognized:
*
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 };
// 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:
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;
return urls.map(function (url) {
url = url.trim();
- if (/^(\.{0,2}|~)(\/|$)/.test(url) || util.OS.isWindows && /^[a-z]:/i.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 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)
+ if (proto && services.PROTOCOL + proto[1] in Cc)
return url;
// Check for a matching search keyword.
}, this);
},
stringToURLArray: deprecated("dactyl.parseURLs", "parseURLs"),
- urlish: Class.memoize(function () util.regexp(<![CDATA[
+ urlish: Class.Memoize(function () util.regexp(<![CDATA[
^ (
<domain>+ (:\d+)? (/ .*) |
<domain>+ (:\d+) |
/**
* Restart the host application.
*/
- restart: function () {
+ restart: function (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
return [];
}
},
-
wrapCallback: function (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;
}, {
toolbarHidden: function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true"
}, {
+ cache: function () {
+ cache.register("help/plugins.xml", function () {
+ // Process plugin help entries.
+ XML.ignoreWhiteSpace = XML.prettyPrinting = false;
+
+ let body = XML();
+ for (let [, context] in Iterator(plugins.contexts))
+ try {
+ let info = contexts.getDocs(context);
+ if (info instanceof XML) {
+ 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 (let attr in values(["@name", "@summary", "@href"]))
+ if (elem[attr].length())
+ info[attr] = elem[attr];
+ }
+ body += <h2 xmlns={NS.uri} tag={info.@name + '-plugin'}>{info.@summary}</h2> +
+ info;
+ }
+ }
+ catch (e) {
+ util.reportError(e);
+ }
+
+ return '<?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' +
+ <document xmlns={NS}
+ name="plugins" title={config.appName + " Plugins"}>
+ <h1 tag="using-plugins">{_("help.title.Using Plugins")}</h1>
+ <toc start="2"/>
+
+ {body}
+ </document>.toXMLString();
+ });
+
+ cache.register("help/index.xml", function () {
+ default xml namespace = NS;
+
+ return '<?xml version="1.0"?>\n' +
+ <overlay xmlns={NS}>{
+ template.map(dactyl.indices, function ([name, iter])
+ <dl insertafter={name + "-index"}>{
+ template.map(iter(), util.identity)
+ }</dl>, <>{"\n\n"}</>)
+ }</overlay>;
+ });
+
+ cache.register("help/gui.xml", function () {
+ default xml namespace = NS;
+
+ return '<?xml version="1.0"?>\n' +
+ <overlay xmlns={NS}>
+ <dl insertafter="dialog-list">{
+ template.map(config.dialogs, function ([name, val])
+ (!val[2] || val[2]())
+ ? <><dt>{name}</dt><dd>{val[0]}</dd></>
+ : undefined,
+ <>{"\n"}</>)
+ }</dl>
+ </overlay>;
+ });
+
+ cache.register("help/privacy.xml", function () {
+ default xml namespace = NS;
+
+ return '<?xml version="1.0"?>\n' +
+ <overlay xmlns={NS}>
+ <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}</dt><dd>{template.linkifyHelp(description, true)}</dd></>,
+ <>{"\n"}</>)
+ }</dl>
+ </overlay>;
+ });
+ },
events: function () {
- events.listen(window, "click", dactyl.closure.onClick, true);
- events.listen(window, "dactyl.execute", dactyl.closure.onExecute, true);
+ events.listen(window, dactyl, "events", true);
},
// Only general options are added here, which are valid for all Dactyl extensions
options: function () {
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 (!Dactyl.toolbarHidden(document.getElementById(v[1][0])))].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")) {
{
setter: function (value) {
prefs.safeSet("accessibility.typeaheadfind.enablesound", !value,
- _("option.visualbell.safeSet"));
+ _("option.safeSet", "visualbell"));
return value;
}
});
},
mappings: function () {
- mappings.add([modes.MAIN], ["<open-help>", "<F1>"],
- "Open the introductory help page",
- function () { dactyl.help(); });
-
- mappings.add([modes.MAIN], ["<open-single-help>", "<A-F1>"],
- "Open the single, consolidated help page",
- function () { ex.helpall(); });
-
if (dactyl.has("session"))
mappings.add([modes.NORMAL], ["ZQ"],
"Quit and don't save the session",
"Execute the specified menu item from the command line",
function (args) {
let arg = args[0] || "";
- let items = dactyl.menuItems;
+ let items = dactyl.getMenuItems(arg);
dactyl.assert(items.some(function (i) i.dactylPath == arg),
_("emenu.notFound", arg));
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 or matching plugins",
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)
storage.session.rehashCmd = args.trailing; // Hack.
args.break = true;
+
+ if (args["+purgecaches"])
+ cache.flush();
+
util.rehash(args);
},
{
argCount: "0", // FIXME
- 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
- }
- ]
+ options: startupOptions
});
commands.add(["res[tart]"],
- "Force " + config.appName + " to restart",
- function () { dactyl.restart(); },
- { argCount: "0" });
+ "Force " + config.host + " to restart",
+ function (args) {
+ if (args["+purgecaches"])
+ cache.flush();
- function findToolbar(name) util.evaluateXPath(
+ 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);
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: </div> +
+ <div>{navigator.userAgent}</div>)
+ }
}, {
argCount: "0",
bang: true
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;
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) {
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;
+
dactyl.timeout(function () {
try {
- var args = storage.session.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();