X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fcontent%2Fdactyl.js;h=02ce9bd177eec85c419f7875c35bdd8caac1e2be;hb=8b6fcae7eaa413bc62d645d2d0c99835c47265e6;hp=6296b9ac6d26a5db35ccee446d4ecd3c606de94d;hpb=eeed0be1a8abf7e3c97f43b63c1d595e940fef21;p=dactyl.git diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 6296b9a..02ce9bd 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott // Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2011 by Kris Maglione +// Copyright (c) 2008-2012 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -8,13 +8,6 @@ /** @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"; @@ -23,7 +16,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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 = {}; @@ -32,46 +26,77 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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 { @@ -82,23 +107,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { } }, - /** @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", { @@ -106,7 +125,37 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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", @@ -114,8 +163,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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 }), @@ -144,7 +202,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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) { @@ -157,7 +215,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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) { @@ -184,14 +241,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, 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)); @@ -200,11 +260,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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; @@ -218,11 +279,11 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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; } @@ -241,20 +302,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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: <> - - - , - append: <> - - - + 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"; @@ -275,16 +331,20 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * 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); @@ -303,15 +363,22 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * 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]; } @@ -346,12 +413,14 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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(); @@ -399,56 +468,63 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * @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] + " })"); @@ -463,7 +539,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * @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; @@ -483,19 +559,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, 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); }, /** @@ -559,34 +623,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * @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 @@ -603,339 +640,20 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { } }, + 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 +=

{context.INFO.@summary}

+ - context.INFO; - } - - let help = - '\n' + - '\n' + - '\n' + - unescape(encodeURI( // UTF-8 handling hack. - -

Using Plugins

- - - {body} -
.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( \s* # .*\n) - - | ^ (?P \s*) - (?P [-•*+]) \ // - (?P .*\n - (?: \2\ \ .*\n | \s*\n)* ) - - | (?P - (?: ^ [^\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 =
    ; - let 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 +=

    {template.linkifyHelp(par.slice(0, -1), true)}

    ; - else { - let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par); - res +=

    { - !tags.length ? "" : - {tags.join(" ")} - }{ - a ? {a} : "" - }{ - template.linkifyHelp(b, true) - }

    ; - } - } - 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", - '\n' + - '\n' + - '\n' + - unescape(encodeURI( // UTF-8 handling hack. - -

    {config.appName} Versions

    - - - {body} -
    .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', - '\n' + - '\n' + - unescape(encodeURI( // UTF-8 handling hack. - template.map(dactyl.indices, function ([name, iter]) -
    { - template.map(iter(), util.identity) - }
    , <>{"\n\n"}))) + - '\n
    ']; + 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(.toXMLString()); - Array.map(node.childNodes, fix); - 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 = [ - '\n', - '\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. * @@ -945,117 +663,94 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * @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) {cmd}; + link = function (cmd) ["ex", {}, cmd]; args = obj.parseArgs("", CompletionContext(str || "")); - spec = function (cmd) cmd + (obj.bang ? ! : <>); + 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 ? <>count{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 = {extra}; + 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) {name}; + 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 = -
    {link(obj.helpTag || obj.name, obj.name)}
    { - template.linkifyHelp(obj.description ? obj.description.replace(/\.$/, "") : "", true) - }
    ; + 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.* += <> - - {template.map(obj.names.slice().reverse(), tag, " ")} - { - spec(template.highlightRegexp((obj.specs || obj.names)[0], - /\[(.*?)\]/g, - function (m, n0) {n0})) - }{ - !obj.type ? "" : <> - {obj.type} - {obj.stringDefaultValue}} - { - obj.description ? br +

    {template.linkifyHelp(obj.description.replace(/\.?$/, "."), true)}

    : "" }{ - extraHelp ? br + extraHelp : "" }{ - !(extraHelp || obj.description) ? br +

    Sorry, no help available.

    : "" } -
    -
    ; + 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 + <> ) - <>
    { br + template.map(ary, function ([a, b]) <>
    {a}
    {b}
    , br) } -
    - ; + 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) {n}, <>, ) - }) - } + [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) }); }, /** @@ -1064,11 +759,11 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * 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)); @@ -1076,13 +771,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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); @@ -1122,8 +820,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * @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, _)) @@ -1133,35 +831,45 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { } }, - 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: * @@ -1182,12 +890,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * 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); @@ -1202,8 +910,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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)) @@ -1215,30 +924,48 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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; } } @@ -1248,10 +975,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { // 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; @@ -1270,7 +995,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * ['www.google.com/search?q=bla', 'www.osnews.com'] * * @param {string} str - * @returns {string[]} + * @returns {[string]} */ parseURLs: function parseURLs(str) { let urls; @@ -1283,20 +1008,20 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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); @@ -1313,15 +1038,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, this); }, stringToURLArray: deprecated("dactyl.parseURLs", "parseURLs"), - urlish: Class.memoize(function () util.regexp(+ (:\d+)? (/ .*) | + (:\d+) | + \. [a-z0-9]+ | localhost ) $ - ]]>, "ix", { - domain: util.regexp(String.replace(, /U/g, "\\u"), "x") + */), /U/g, "\\u"), "x") })), pluginFiles: {}, @@ -1358,7 +1083,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * @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; @@ -1375,10 +1100,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { /** * 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); }, @@ -1397,7 +1124,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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; } }, @@ -1412,7 +1144,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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) @@ -1424,8 +1157,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { util.reportError(error); return; } + if (error.result == Cr.NS_BINDING_ABORTED) return; + if (echo) dactyl.echoerr(error, commandline.FORCE_SINGLELINE); else @@ -1441,7 +1176,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * @returns {Object} * @see Commands#parseArgs */ - parseCommandLine: function (cmdline) { + parseCommandLine: function parseCommandLine(cmdline) { try { return commands.get("rehash").parseArgs(cmdline); } @@ -1450,10 +1185,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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; @@ -1470,55 +1204,95 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, /** - * @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 '\n' + + '\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 '\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 '\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 '\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); @@ -1542,7 +1316,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { M: ["Always show messages outside of the status line"] }, setter: function (opts) { - if (loaded.commandline) + if (loaded.commandline || ~opts.indexOf("c")) commandline.widgets.updateVisibility(); } }, @@ -1578,7 +1352,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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")) @@ -1611,12 +1385,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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(), @@ -1640,23 +1414,27 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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; } } @@ -1670,7 +1448,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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"], @@ -1684,21 +1462,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { { setter: function (value) { prefs.safeSet("accessibility.typeaheadfind.enablesound", !value, - "See 'visualbell' option"); + _("option.safeSet", "visualbell")); return value; } }); }, - mappings: function () { - mappings.add([modes.MAIN], [""], - "Open the introductory help page", - function () { dactyl.help(); }); - - mappings.add([modes.MAIN], [""], - "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", @@ -1709,7 +1479,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { function () { dactyl.quit(true); }); }, - commands: function () { + commands: function initCommands() { commands.add(["dia[log]"], "Open a " + config.appName + " dialog", function (args) { @@ -1727,7 +1497,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { } }, { argCount: "1", - bang: true, completer: function (context) { context.ignoreCase = true; completion.dialog(context); @@ -1738,14 +1507,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { "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", @@ -1768,32 +1539,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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); }, @@ -1802,7 +1549,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { bang: true, keepQuotes: true, serialGroup: 10, - serialize: function () [ + serialize: function () [ { command: this.name, literalArg: options["loadplugins"].join(" ") @@ -1819,6 +1566,30 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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) { @@ -1833,53 +1604,72 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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) { @@ -1888,7 +1678,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { action(toolbar); events.checkFocus(); }, { - argcount: "1", + argCount: "1", completer: function (context) { completion.toolbar(context); if (filter) @@ -1900,12 +1690,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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"], @@ -1916,9 +1706,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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) { @@ -1927,7 +1717,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { for (let i in util.interruptibleRange(0, count, 500)) { let now = Date.now(); - method(); + func(); total += Date.now() - now; } @@ -1951,18 +1741,28 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { totalUnits = "msec"; commandline.commandOutput( - - - - - - - -
    Code execution summary
      Executed:{count}times
      Average time:{each.toFixed(2)}{eachUnits}
      Total time:{total.toFixed(2)}{totalUnits}
    ); + ["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; @@ -1979,7 +1779,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { dactyl.echoerr(e); } }, { - argCount: "+", + argCount: "1", bang: true, completer: function (context) { if (/^:/.test(context.filter)) @@ -2010,7 +1810,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { vbs.setFrom = setFrom; } }, { - argCount: "+", + argCount: "1", completer: function (context) completion.ex(context), count: true, literal: 0, @@ -2022,10 +1822,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { function (args) { if (args.bang) dactyl.open("about:"); - else - commandline.commandOutput(<> - {config.appName} {config.version} running on:
    {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 @@ -2033,34 +1838,29 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, - 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) { @@ -2069,14 +1869,38 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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); @@ -2094,20 +1918,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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); @@ -2149,7 +1970,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { if (dactyl.commandLineOptions.rcFile == "NONE" || dactyl.commandLineOptions.noPlugins) options["loadplugins"] = []; - if (options["loadplugins"]) + if (options["loadplugins"].length) dactyl.loadPlugins(); } catch (e) { @@ -2167,9 +1988,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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);