diff options
-rw-r--r-- | .hgignore | 2 | ||||
-rw-r--r-- | common/chrome.manifest | 7 | ||||
-rw-r--r-- | common/components/commandline-handler.js | 45 | ||||
-rw-r--r-- | common/components/protocols.js | 384 | ||||
-rw-r--r-- | common/content/dactyl.js | 410 | ||||
-rw-r--r-- | common/content/help.xsl | 8 | ||||
-rw-r--r-- | common/content/mappings.js | 6 | ||||
-rw-r--r-- | common/locale/en-US/buffer.xml | 2 | ||||
-rw-r--r-- | common/modules/base.jsm | 75 | ||||
-rw-r--r-- | common/modules/bootstrap.jsm | 22 | ||||
-rw-r--r-- | common/modules/commands.jsm | 4 | ||||
-rw-r--r-- | common/modules/config.jsm | 38 | ||||
-rw-r--r-- | common/modules/help.jsm | 326 | ||||
-rw-r--r-- | common/modules/io.jsm | 4 | ||||
-rw-r--r-- | common/modules/overlay.jsm | 4 | ||||
-rw-r--r-- | common/modules/protocol.jsm | 228 | ||||
-rw-r--r-- | common/modules/sanitizer.jsm | 2 | ||||
-rw-r--r-- | common/modules/services.jsm | 1 | ||||
-rw-r--r-- | common/modules/template.jsm | 10 |
19 files changed, 797 insertions, 781 deletions
@@ -15,6 +15,8 @@ syntax: glob downloads/* .git/* +common/tests/functional/log + *.py[co] ## Editor backup and swap files diff --git a/common/chrome.manifest b/common/chrome.manifest index 4995b0e8..b55978f4 100644 --- a/common/chrome.manifest +++ b/common/chrome.manifest @@ -13,10 +13,3 @@ component {16dc34f7-6d22-4aa4-a67f-2921fb5dcb69} components/commandline-handler. contract @mozilla.org/commandlinehandler/general-startup;1?type=dactyl {16dc34f7-6d22-4aa4-a67f-2921fb5dcb69} category command-line-handler m-dactyl @mozilla.org/commandlinehandler/general-startup;1?type=dactyl -component {c1b67a07-18f7-4e13-b361-2edcc35a5a0d} components/protocols.js -contract @mozilla.org/network/protocol;1?name=chrome-data {c1b67a07-18f7-4e13-b361-2edcc35a5a0d} -component {9c8f2530-51c8-4d41-b356-319e0b155c44} components/protocols.js -contract @mozilla.org/network/protocol;1?name=dactyl {9c8f2530-51c8-4d41-b356-319e0b155c44} -component {f4506a17-5b4d-4cd9-92d4-2eb4630dc388} components/protocols.js -contract @dactyl.googlecode.com/base/xpc-interface-shim {f4506a17-5b4d-4cd9-92d4-2eb4630dc388} - diff --git a/common/components/commandline-handler.js b/common/components/commandline-handler.js index ba2c578c..2bd10f9b 100644 --- a/common/components/commandline-handler.js +++ b/common/components/commandline-handler.js @@ -11,35 +11,54 @@ function reportError(e) { var global = this; var NAME = "command-line-handler"; -var Cc = Components.classes; -var Ci = Components.interfaces; -var Cu = Components.utils; +var { classes: Cc, interfaces: Ci, utils: Cu } = Components; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -function CommandLineHandler() { - this.wrappedJSObject = this; - +function init() { + Cu.import("resource://dactyl/bootstrap.jsm"); + if (!JSMLoader.initialized) + JSMLoader.init(); Cu.import("resource://dactyl/base.jsm"); - require(global, "util"); require(global, "config"); + require(global, "util"); +} + +function CommandLineHandler() { + this.wrappedJSObject = this; } CommandLineHandler.prototype = { - classDescription: "Dactyl Command-line Handler", + classDescription: "Dactyl command line Handler", classID: Components.ID("{16dc34f7-6d22-4aa4-a67f-2921fb5dcb69}"), contractID: "@mozilla.org/commandlinehandler/general-startup;1?type=dactyl", - _xpcom_categories: [{ - category: "command-line-handler", - entry: "m-dactyl" - }], + _xpcom_categories: [ + { + category: "command-line-handler", + entry: "m-dactyl" + }, + + // FIXME: Belongs elsewhere + { + category: "profile-after-change", + entry: "m-dactyl" + } + ], + + observe: function observe(subject, topic, data) { + if (topic === "profile-after-change") { + init(); + require(global, "overlay"); + } + }, - QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsICommandLineHandler]), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsICommandLineHandler]), handle: function (commandLine) { + init(); try { var remote = commandLine.handleFlagWithParam(config.name + "-remote", false); } diff --git a/common/components/protocols.js b/common/components/protocols.js deleted file mode 100644 index 78691973..00000000 --- a/common/components/protocols.js +++ /dev/null @@ -1,384 +0,0 @@ -// Copyright (c) 2008-2011 Kris Maglione <maglione.k at Gmail> -// -// This work is licensed for reuse under an MIT license. Details are -// given in the LICENSE.txt file included with this file. -"use strict"; -function reportError(e) { - dump("dactyl: protocols: " + e + "\n" + (e.stack || Error().stack)); - Cu.reportError(e); -} - -var NAME = "protocols"; -var global = this; -var Cc = Components.classes; -var Ci = Components.interfaces; -var Cu = Components.utils; - -var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); -var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].getService(Ci.nsIPrincipal); - -var DNE = "resource://dactyl/content/does/not/exist"; -var _DNE; - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); - -function makeChannel(url, orig) { - try { - if (url == null) - return fakeChannel(orig); - - if (typeof url === "function") - return let ([type, data] = url(orig)) StringChannel(data, type, orig); - - if (isArray(url)) - return let ([type, data] = url) StringChannel(data, type, orig); - - let uri = ioService.newURI(url, null, null); - return (new XMLChannel(uri)).channel; - } - catch (e) { - util.reportError(e); - throw e; - } -} -function fakeChannel(orig) { - let channel = ioService.newChannel(DNE, null, null); - channel.originalURI = orig; - return channel; -} -function redirect(to, orig, time) { - let html = <html><head><meta http-equiv="Refresh" content={(time || 0) + ";" + to}/></head></html>.toXMLString(); - return StringChannel(html, "text/html", ioService.newURI(to, null, null)); -} - -function Factory(clas) ({ - __proto__: clas.prototype, - createInstance: function (outer, iid) { - try { - if (outer != null) - throw Components.results.NS_ERROR_NO_AGGREGATION; - if (!clas.instance) - clas.instance = new clas(); - return clas.instance.QueryInterface(iid); - } - catch (e) { - reportError(e); - throw e; - } - } -}); - -/* Adds support for data: URIs with chrome privileges - * and fragment identifiers. - * - * "chrome-data:" <content-type> [; <flag>]* "," [<data>] - * - * By Kris Maglione, ideas from Ed Anuff's nsChromeExtensionHandler. - */ -function ChromeData() {} -ChromeData.prototype = { - contractID: "@mozilla.org/network/protocol;1?name=chrome-data", - classID: Components.ID("{c1b67a07-18f7-4e13-b361-2edcc35a5a0d}"), - classDescription: "Data URIs with chrome privileges", - QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]), - _xpcom_factory: Factory(ChromeData), - - scheme: "chrome-data", - defaultPort: -1, - allowPort: function (port, scheme) false, - protocolFlags: Ci.nsIProtocolHandler.URI_NORELATIVE - | Ci.nsIProtocolHandler.URI_NOAUTH - | Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE, - - newURI: function (spec, charset, baseURI) { - var uri = Components.classes["@mozilla.org/network/standard-url;1"] - .createInstance(Components.interfaces.nsIStandardURL) - .QueryInterface(Components.interfaces.nsIURI); - uri.init(uri.URLTYPE_STANDARD, this.defaultPort, spec, charset, null); - return uri; - }, - - newChannel: function (uri) { - try { - if (uri.scheme == this.scheme) { - let channel = ioService.newChannel(uri.spec.replace(/^.*?:\/*(.*)(?:#.*)?/, "data:$1"), - null, null); - channel.contentCharset = "UTF-8"; - channel.owner = systemPrincipal; - channel.originalURI = uri; - return channel; - } - } - catch (e) {} - return fakeChannel(uri); - } -}; - -function Dactyl() { - // Kill stupid validator warning. - this["wrapped" + "JSObject"] = this; - - this.HELP_TAGS = {}; - this.FILE_MAP = {}; - this.OVERLAY_MAP = {}; - - this.pages = {}; - this.providers = {}; - - Cu.import("resource://dactyl/bootstrap.jsm"); - if (!JSMLoader.initialized) - JSMLoader.init(); - JSMLoader.load("base.jsm", global); - require(global, "config"); - require(global, "services"); - require(global, "util"); - _DNE = ioService.newChannel(DNE, null, null).name; - - // Doesn't belong here: - AboutHandler.prototype.register(); -} -Dactyl.prototype = { - contractID: "@mozilla.org/network/protocol;1?name=dactyl", - classID: Components.ID("{9c8f2530-51c8-4d41-b356-319e0b155c44}"), - classDescription: "Dactyl utility protocol", - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsIProtocolHandler]), - _xpcom_factory: Factory(Dactyl), - - init: function (obj) { - for each (let prop in ["HELP_TAGS", "FILE_MAP", "OVERLAY_MAP"]) { - this[prop] = this[prop].constructor(); - for (let [k, v] in Iterator(obj[prop] || {})) - this[prop][k] = v; - } - this.initialized = true; - }, - - scheme: "dactyl", - defaultPort: -1, - allowPort: function (port, scheme) false, - protocolFlags: 0 - | Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE - | Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE, - - newURI: function newURI(spec, charset, baseURI) { - var uri = Cc["@mozilla.org/network/standard-url;1"] - .createInstance(Ci.nsIStandardURL) - .QueryInterface(Ci.nsIURI); - if (baseURI && baseURI.host === "data") - baseURI = null; - uri.init(uri.URLTYPE_STANDARD, this.defaultPort, spec, charset, baseURI); - return uri; - }, - - newChannel: function newChannel(uri) { - try { - if (/^help/.test(uri.host) && !("all" in this.FILE_MAP)) - return redirect(uri.spec, uri, 1); - - if (uri.host in this.providers) - return makeChannel(this.providers[uri.host](uri), uri); - - let path = decodeURIComponent(uri.path.replace(/^\/|#.*/g, "")); - switch(uri.host) { - case "content": - return makeChannel(this.pages[path] || "resource://dactyl-content/" + path, uri); - case "data": - try { - var channel = ioService.newChannel(uri.path.replace(/^\/(.*)(?:#.*)?/, "data:$1"), - null, null); - } - catch (e) { - var error = e; - break; - } - channel.contentCharset = "UTF-8"; - channel.owner = systemPrincipal; - channel.originalURI = uri; - return channel; - case "help": - return makeChannel(this.FILE_MAP[path], uri); - case "help-overlay": - return makeChannel(this.OVERLAY_MAP[path], uri); - case "help-tag": - let tag = decodeURIComponent(uri.path.substr(1)); - if (tag in this.FILE_MAP) - return redirect("dactyl://help/" + tag, uri); - if (tag in this.HELP_TAGS) - return redirect("dactyl://help/" + this.HELP_TAGS[tag] + "#" + tag.replace(/#/g, encodeURIComponent), uri); - break; - case "locale": - return LocaleChannel("dactyl-locale", path, uri); - case "locale-local": - return LocaleChannel("dactyl-local-locale", path, uri); - } - } - catch (e) { - util.reportError(e); - } - if (error) - throw error; - return fakeChannel(uri); - }, - - // FIXME: Belongs elsewhere - _xpcom_categories: [{ - category: "profile-after-change", - entry: "m-dactyl" - }], - - observe: function observe(subject, topic, data) { - if (topic === "profile-after-change") { - Cu.import("resource://dactyl/bootstrap.jsm"); - JSMLoader.init(); - require(global, "overlay"); - } - } -}; - -function LocaleChannel(pkg, path, orig) { - for each (let locale in [config.locale, "en-US"]) - for each (let sep in "-/") { - var channel = makeChannel(["resource:/", pkg + sep + config.locale, path].join("/"), orig); - if (channel.name !== _DNE) - return channel; - } - return channel; -} - -function StringChannel(data, contentType, uri) { - let channel = services.StreamChannel(uri); - channel.contentStream = services.CharsetConv("UTF-8").convertToInputStream(data); - if (contentType) - channel.contentType = contentType; - channel.contentCharset = "UTF-8"; - channel.owner = systemPrincipal; - if (uri) - channel.originalURI = uri; - return channel; -} - -function XMLChannel(uri, contentType) { - try { - var channel = services.io.newChannelFromURI(uri); - var channelStream = channel.open(); - } - catch (e) { - this.channel = fakeChannel(uri); - return; - } - - this.uri = uri; - this.sourceChannel = services.io.newChannelFromURI(uri); - this.pipe = services.Pipe(true, true, 0, 0, null); - this.writes = []; - - this.channel = services.StreamChannel(uri); - this.channel.contentStream = this.pipe.inputStream; - this.channel.contentType = contentType || channel.contentType; - this.channel.contentCharset = "UTF-8"; - this.channel.owner = systemPrincipal; - - let stream = services.InputStream(channelStream); - let [, pre, doctype, url, open, post] = util.regexp(<![CDATA[ - ^ ([^]*?) - (?: - (<!DOCTYPE \s+ \S+ \s+) SYSTEM \s+ "([^"]*)" - (\s+ \[)? - ([^]*) - )? - $ - ]]>, "x").exec(stream.read(4096)); - this.writes.push(pre); - if (doctype) { - this.writes.push(doctype + "[\n"); - try { - this.writes.push(services.io.newChannel(url, null, null).open()); - } - catch (e) {} - if (!open) - this.writes.push("\n]"); - this.writes.push(post); - } - this.writes.push(channelStream); - - this.writeNext(); -} -XMLChannel.prototype = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver]), - writeNext: function () { - try { - if (!this.writes.length) - this.pipe.outputStream.close(); - else { - let stream = this.writes.shift(); - if (isString(stream)) - stream = services.StringStream(stream); - - services.StreamCopier(stream, this.pipe.outputStream, null, - false, true, 4096, true, false) - .asyncCopy(this, null); - } - } - catch (e) { - util.reportError(e); - } - }, - - onStartRequest: function (request, context) {}, - onStopRequest: function (request, context, statusCode) { - this.writeNext(); - } -}; - -function AboutHandler() {} -AboutHandler.prototype = { - register: function () { - try { - JSMLoader.registerFactory(Factory(AboutHandler)); - } - catch (e) { - util.reportError(e); - } - }, - - get classDescription() "About " + config.appName + " Page", - - classID: Components.ID("81495d80-89ee-4c36-a88d-ea7c4e5ac63f"), - - get contractID() "@mozilla.org/network/protocol/about;1?what=" + config.name, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]), - - newChannel: function (uri) { - let channel = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService) - .newChannel("dactyl://content/about.xul", null, null); - channel.originalURI = uri; - return channel; - }, - - getURIFlags: function (uri) Ci.nsIAboutModule.ALLOW_SCRIPT, -}; - -// A hack to get information about interfaces. -// Doesn't belong here. -function Shim() {} -Shim.prototype = { - contractID: "@dactyl.googlecode.com/base/xpc-interface-shim", - classID: Components.ID("{f4506a17-5b4d-4cd9-92d4-2eb4630dc388}"), - classDescription: "XPCOM empty interface shim", - QueryInterface: function (iid) { - if (iid.equals(Ci.nsISecurityCheckedComponent)) - throw Components.results.NS_ERROR_NO_INTERFACE; - return this; - }, - getHelperForLanguage: function () null, - getInterfaces: function (count) { count.value = 0; } -}; - -if (XPCOMUtils.generateNSGetFactory) - var NSGetFactory = XPCOMUtils.generateNSGetFactory([ChromeData, Dactyl, Shim]); -else - var NSGetModule = XPCOMUtils.generateNSGetModule([ChromeData, Dactyl, Shim]); -var EXPORTED_SYMBOLS = ["NSGetFactory", "global"]; - -// vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/dactyl.js b/common/content/dactyl.js index f5bb119d..1c16719c 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -29,10 +29,6 @@ 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(); }; @@ -287,10 +283,10 @@ 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)) + if (!haveTag(obj.helpTag)) res[1].@tag = obj.helpTag; yield res; @@ -631,33 +627,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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 */ initDocument: function initDocument(doc) { @@ -672,243 +641,89 @@ 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) { - // 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); - } + initHelp: function initHelp(force) { + if ("noscriptOverlay" in window) + 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 DOM.XPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc)) - for (let tag in values((elem.value || elem.textContent).split(/\s+/))) - tagMap[tag] = file; - }; + if (!force && help.initialized) + return; - 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 DOM.XPath("//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); - }); - }); + help.initialize(force); - // Process plugin help entries. - XML.ignoreWhiteSpace = XML.prettyPrinting = false; + // 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)); + 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); + 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; + 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); - } + } + catch (e) { + util.reportError(e); + } - 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; - } + help.files["plugins"] = function () ['text/xml;charset=UTF-8', + '<?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"/> - 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"; - } - } + {body} + </document>.toXMLString()]; - 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; - } + + default xml namespace = NS; + + help.overlays["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>]; + + help.overlays["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>]; + + help.tags["plugins"] = help.tags["plugins.xml"] = "plugins"; + help.tags["index"] = help.tags["index.xml"] = "index"; + + help.addTags("plugins", util.httpGet("dactyl://help/plugins").responseXML); + help.addTags("index", util.httpGet("dactyl://help-overlay/index").responseXML); }, stringifyXML: function (xml) { @@ -995,7 +810,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { let chromeFiles = {}; let styles = {}; - for (let [file, ] in Iterator(services["dactyl:"].FILE_MAP)) { + for (let [file, ] in Iterator(help.files)) { let url = "dactyl://help/" + file; dactyl.open(url); util.waitFor(function () content.location.href == url && buffer.loaded @@ -1018,7 +833,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { .join("\n"); addDataEntry("help.css", data.replace(/chrome:[^ ")]+\//g, "")); - addDataEntry("tag-map.json", JSON.stringify(services["dactyl:"].HELP_TAGS)); + addDataEntry("tag-map.json", JSON.stringify(help.tags)); let m, re = /(chrome:[^ ");]+\/)([^ ");]+)/g; while ((m = re.exec(data))) @@ -1047,12 +862,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { let args = null; if (obj instanceof Command) { - link = function (cmd) <ex>:{cmd}</ex>; + link = function (cmd) <ex>{cmd}</ex>; args = obj.parseArgs("", CompletionContext(str || "")); tag = function (cmd) <>:{cmd}</>; spec = function (cmd) <>{ obj.count ? <oa>count</oa> : <></> - }:{ + }{ cmd }{ obj.bang ? <oa>!</oa> : <></> @@ -1085,7 +900,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { </>; 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) @@ -1095,9 +910,11 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { <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>)) + 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> @@ -1139,30 +956,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, /** - * 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. * * These are set and accessed with the "g:" prefix. @@ -1764,14 +1557,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, 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", @@ -1842,30 +1627,6 @@ 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 or matching plugins", function (args) { @@ -2123,15 +1884,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { 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; @@ -2186,7 +1938,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { localPrefs.set("first-run", false); this.withSavedValues(["forceTarget"], function () { this.forceTarget = dactyl.NEW_TAB; - this.help(); + help.help(); }); }, 1000); diff --git a/common/content/help.xsl b/common/content/help.xsl index 1ebd6d6b..132ed738 100644 --- a/common/content/help.xsl +++ b/common/content/help.xsl @@ -296,14 +296,18 @@ <xsl:template name="linkify-tag"> <xsl:param name="contents" select="text()"/> <xsl:variable name="tag" select="$contents"/> + <xsl:variable name="tag-url" select=" + regexp:replace(regexp:replace($tag, '%', 'g', '%25'), + '#', 'g', '%23')"/> + <a style="color: inherit;"> <xsl:if test="not(@link) or @link != 'false'"> <xsl:choose> <xsl:when test="contains(ancestor::*/@document-tags, concat(' ', $tag, ' '))"> - <xsl:attribute name="href">#<xsl:value-of select="$tag"/></xsl:attribute> + <xsl:attribute name="href">#<xsl:value-of select="$tag-url"/></xsl:attribute> </xsl:when> <xsl:otherwise> - <xsl:attribute name="href">dactyl://help-tag/<xsl:value-of select="$tag"/></xsl:attribute> + <xsl:attribute name="href">dactyl://help-tag/<xsl:value-of select="$tag-url"/></xsl:attribute> </xsl:otherwise> </xsl:choose> </xsl:if> diff --git a/common/content/mappings.js b/common/content/mappings.js index 6fbcead3..b1336161 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -130,7 +130,7 @@ var Map = Class("Map", { if (this.executing) { util.dumpStack(_("map.recursive", args.command)); - throw AssertionError(_("map.recursive", args.command)); + throw FailedAssertion(_("map.recursive", args.command)); } try { @@ -771,10 +771,10 @@ var Mappings = Module("mappings", { name: [mode.char + "listk[eys]", mode.char + "lk"], iterateIndex: function (args) let (self = this, prefix = /^[bCmn]$/.test(mode.char) ? "" : mode.char + "_", - tags = services["dactyl:"].HELP_TAGS) + haveTag = Set.has(help.tags)) ({ helpTag: prefix + map.name, __proto__: map } for (map in self.iterate(args, true)) - if (map.hive === mappings.builtin || Set.has(tags, prefix + map.name))), + if (map.hive === mappings.builtin || haveTag(prefix + map.name))), description: "List all " + mode.displayName + " mode mappings along with their short descriptions", index: mode.char + "-map", getMode: function (args) mode, diff --git a/common/locale/en-US/buffer.xml b/common/locale/en-US/buffer.xml index e76ee24f..7e1b645b 100644 --- a/common/locale/en-US/buffer.xml +++ b/common/locale/en-US/buffer.xml @@ -140,7 +140,7 @@ </item> <item> - <tags><scroll-percent> N%</tags> + <tags><scroll-percent> N% %</tags> <spec><a>count</a>%</spec> <description short="true"> <p>Scroll to <a>count</a> percent of the document.</p> diff --git a/common/modules/base.jsm b/common/modules/base.jsm index b7b7d441..7c72334a 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -133,7 +133,8 @@ function defineModule(name, params, module) { module.NAME = name; module.EXPORTED_SYMBOLS = params.exports || []; - defineModule.loadLog.push("defineModule " + name); + defineModule.loadLog.push("[Begin " + name + "]"); + defineModule.prefix += " "; for (let [, mod] in Iterator(params.require || [])) require(module, mod); @@ -152,11 +153,13 @@ function defineModule(name, params, module) { defineModule.loadLog = []; Object.defineProperty(defineModule.loadLog, "push", { value: function (val) { + val = defineModule.prefix + val; if (true) defineModule.dump(val + "\n"); this[this.length] = Date.now() + " " + val; } }); +defineModule.prefix = ""; defineModule.dump = function dump_() { let msg = Array.map(arguments, function (msg) { if (loaded.util && typeof msg == "object") @@ -185,7 +188,8 @@ defineModule.time = function time(major, minor, func, self) { } function endModule() { - defineModule.loadLog.push("endModule " + currentModule.NAME); + defineModule.prefix = defineModule.prefix.slice(0, -2); + defineModule.loadLog.push("(End " + currentModule.NAME + ")"); for (let [, mod] in Iterator(use[currentModule.NAME] || [])) require(mod, currentModule.NAME, "use"); @@ -202,7 +206,7 @@ function require(obj, name, from) { let caller = Components.stack.caller; if (!loaded[name]) - defineModule.loadLog.push(" " + (from || "require") + ": loading " + name + " into " + (obj.NAME || caller.filename + ":" + caller.lineNumber)); + defineModule.loadLog.push((from || "require") + ": loading " + name + " into " + (obj.NAME || caller.filename + ":" + caller.lineNumber)); JSMLoader.load(name + ".jsm", obj); return obj; @@ -220,11 +224,12 @@ defineModule("base", { // sed -n 's/^(const|function) ([a-zA-Z0-9_]+).*/ "\2",/p' base.jsm | sort | fmt exports: [ "ErrorBase", "Cc", "Ci", "Class", "Cr", "Cu", "Module", "JSMLoader", "Object", "Runnable", - "Set", "Struct", "StructBase", "Timer", "UTF8", "XPCOM", "XPCOMUtils", "XPCSafeJSObjectWrapper", - "array", "bind", "call", "callable", "ctypes", "curry", "debuggerProperties", "defineModule", - "deprecated", "endModule", "forEach", "isArray", "isGenerator", "isinstance", "isObject", - "isString", "isSubclass", "iter", "iterAll", "iterOwnProperties", "keys", "memoize", "octal", - "properties", "require", "set", "update", "values", "withCallerGlobal" + "Set", "Struct", "StructBase", "Timer", "UTF8", "XPCOM", "XPCOMShim", "XPCOMUtils", + "XPCSafeJSObjectWrapper", "array", "bind", "call", "callable", "ctypes", "curry", + "debuggerProperties", "defineModule", "deprecated", "endModule", "forEach", "isArray", + "isGenerator", "isinstance", "isObject", "isString", "isSubclass", "iter", "iterAll", + "iterOwnProperties", "keys", "memoize", "octal", "properties", "require", "set", "update", + "values", "withCallerGlobal" ], use: ["config", "services", "util"] }, this); @@ -754,6 +759,10 @@ function Class() { constructor: { value: Constructor }, }); self.instance = self; + + if ("_metaInit_" in self && self._metaInit_) + self._metaInit_.apply(self, arguments); + var res = self.init.apply(self, arguments); return res !== undefined ? res : self; })]]>, @@ -1022,7 +1031,7 @@ function XPCOM(interfaces, superClass) { interfaces = Array.concat(interfaces); let shim = interfaces.reduce(function (shim, iface) shim.QueryInterface(iface), - Cc["@dactyl.googlecode.com/base/xpc-interface-shim"].createInstance()); + XPCOMShim()); let res = Class("XPCOM(" + interfaces + ")", superClass || Class, update( iter.toObject([k, v === undefined || callable(v) ? function stub() null : v] @@ -1031,6 +1040,18 @@ function XPCOM(interfaces, superClass) { shim = interfaces = null; return res; } +function XPCOMShim() { + let ip = services.InterfacePointer({ + QueryInterface: function (iid) { + if (iid.equals(Ci.nsISecurityCheckedComponent)) + throw Components.results.NS_ERROR_NO_INTERFACE; + return this; + }, + getHelperForLanguage: function () null, + getInterfaces: function (count) { count.value = 0; } + }); + return ip.data; +}; /** * An abstract base class for classes that wish to inherit from Error. @@ -1065,24 +1086,32 @@ var ErrorBase = Class("ErrorBase", Error, { * @returns {Class} */ function Module(name, prototype) { - let init = callable(prototype) ? 4 : 3; - let proto = arguments[callable(prototype) ? 2 : 1]; + try { + let init = callable(prototype) ? 4 : 3; + let proto = arguments[callable(prototype) ? 2 : 1]; - proto._metaInit_ = function () { - delete module.prototype._metaInit_; - currentModule[name.toLowerCase()] = this; - }; + proto._metaInit_ = function () { + delete module.prototype._metaInit_; + currentModule[name.toLowerCase()] = this; + }; - const module = Class.apply(Class, Array.slice(arguments, 0, init)); - let instance = module(); - module.className = name.toLowerCase(); + const module = Class.apply(Class, Array.slice(arguments, 0, init)); + let instance = module(); + module.className = name.toLowerCase(); - instance.INIT = update(Object.create(Module.INIT), - arguments[init] || {}); + instance.INIT = update(Object.create(Module.INIT), + arguments[init] || {}); - currentModule[module.className] = instance; - defineModule.modules.push(instance); - return module; + currentModule[module.className] = instance; + defineModule.modules.push(instance); + return module; + } + catch (e) { + if (typeof e === "string") + e = Error(e); + + dump(e.fileName + ":" + e.lineNumber + ": " + e + "\n" + (e.stack || Error().stack)); + } } Module.INIT = { init: function Module_INIT_init(dactyl, modules, window) { diff --git a/common/modules/bootstrap.jsm b/common/modules/bootstrap.jsm index d51f8696..fc4eac60 100644 --- a/common/modules/bootstrap.jsm +++ b/common/modules/bootstrap.jsm @@ -19,11 +19,11 @@ if (!JSMLoader && "@mozilla.org/fuel/application;1" in Components.classes) .getService(Components.interfaces.extIApplication) .storage.get("dactyl.JSMLoader", null); -if (JSMLoader && JSMLoader.bump === 5) +if (JSMLoader && JSMLoader.bump === 6) JSMLoader.global = this; else JSMLoader = { - bump: 5, + bump: 6, builtin: Cu.Sandbox(this), @@ -167,6 +167,24 @@ else } }, + Factory: function Factory(clas) ({ + __proto__: clas.prototype, + + createInstance: function (outer, iid) { + try { + if (outer != null) + throw Cr.NS_ERROR_NO_AGGREGATION; + if (!clas.instance) + clas.instance = new clas(); + return clas.instance.QueryInterface(iid); + } + catch (e) { + Cu.reportError(e); + throw e; + } + } + }), + registerFactory: function registerFactory(factory) { this.manager.registerFactory(factory.classID, String(factory.classID), diff --git a/common/modules/commands.jsm b/common/modules/commands.jsm index 95d4b2eb..a837ef33 100644 --- a/common/modules/commands.jsm +++ b/common/modules/commands.jsm @@ -12,7 +12,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("commands", { exports: ["ArgType", "Command", "Commands", "CommandOption", "Ex", "commands"], require: ["contexts", "messages", "util"], - use: ["config", "options", "services", "template"] + use: ["config", "help", "options", "services", "template"] }, this); /** @@ -1585,7 +1585,7 @@ var Commands = Module("commands", { cmd.hive == commands.builtin ? "" : <span highlight="Object" style="padding-right: 1em;">{cmd.hive.name}</span> ] })), - iterateIndex: function (args) let (tags = services["dactyl:"].HELP_TAGS) + iterateIndex: function (args) let (tags = help.tags) this.iterate(args).filter(function (cmd) cmd.hive === commands.builtin || Set.has(tags, cmd.helpTag)), format: { headings: ["Command", "Group", "Description"], diff --git a/common/modules/config.jsm b/common/modules/config.jsm index f5d231cb..482216b2 100644 --- a/common/modules/config.jsm +++ b/common/modules/config.jsm @@ -6,16 +6,34 @@ // given in the LICENSE.txt file included with this file. "use strict"; -try { - let global = this; Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("config", { exports: ["ConfigBase", "Config", "config"], - require: ["services", "storage", "util", "template"], + require: ["protocol", "services", "storage", "util", "template"], use: ["io", "messages", "prefs", "styles"] }, this); +function AboutHandler() {} +AboutHandler.prototype = { + get classDescription() "About " + config.appName + " Page", + + classID: Components.ID("81495d80-89ee-4c36-a88d-ea7c4e5ac63f"), + + get contractID() "@mozilla.org/network/protocol/about;1?what=" + config.name, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]), + + newChannel: function (uri) { + let channel = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService) + .newChannel("dactyl://content/about.xul", null, null); + channel.originalURI = uri; + return channel; + }, + + getURIFlags: function (uri) Ci.nsIAboutModule.ALLOW_SCRIPT, +}; + var ConfigBase = Class("ConfigBase", { /** * Called on dactyl startup to allow for any arbitrary application-specific @@ -26,9 +44,19 @@ var ConfigBase = Class("ConfigBase", { if (this.haveGecko("2b")) Set.add(this.features, "Gecko2"); + JSMLoader.registerFactory(JSMLoader.Factory(AboutHandler)); + JSMLoader.registerFactory(JSMLoader.Factory( + Protocol("dactyl", "{9c8f2530-51c8-4d41-b356-319e0b155c44}", + "resource://dactyl-content/"))); + this.timeout(function () { services["dactyl:"].pages.dtd = function () [null, util.makeDTD(config.dtd)]; }); + + update(services["dactyl:"].providers, { + "locale": function (uri, path) LocaleChannel("dactyl-locale", config.locale, path, uri), + "locale-local": function (uri, path) LocaleChannel("dactyl-local-locale", config.locale, path, uri) + }); }, modules: { @@ -41,12 +69,14 @@ var ConfigBase = Class("ConfigBase", { "contexts", "downloads", "finder", + "help", "highlight", "javascript", "messages", "options", "overlay", "prefs", + "protocol", "sanitizer", "services", "storage", @@ -1065,6 +1095,6 @@ config.INIT = update(Object.create(config.INIT), config.INIT, { endModule(); -} catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } +// catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } // vim: set fdm=marker sw=4 sts=4 et ft=javascript: diff --git a/common/modules/help.jsm b/common/modules/help.jsm new file mode 100644 index 00000000..c6dfd445 --- /dev/null +++ b/common/modules/help.jsm @@ -0,0 +1,326 @@ +// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com> +// +// This work is licensed for reuse under an MIT license. Details are +// given in the LICENSE.txt file included with this file. +"use strict"; + +Components.utils.import("resource://dactyl/bootstrap.jsm"); +defineModule("help", { + exports: ["help"], + require: ["protocol", "services", "util"], + use: ["config", "highlight", "messages", "template"] +}, this); + +var Help = Module("Help", { + init: function init() { + this.initialized = false; + this.files = {}; + this.overlays = {}; + this.tags = {}; + + function Loop(fn) + function (uri, path) { + if (!help.initialized) + return RedirectChannel(uri.spec, uri, 1); + return fn.apply(this, arguments); + } + + update(services["dactyl:"].providers, { + "help": Loop(function (uri, path) help.files[path]), + "help-overlay": Loop(function (uri, path) help.overlays[path]), + "help-tag": Loop(function (uri, path) { + let tag = decodeURIComponent(path); + if (tag in help.files) + return RedirectChannel("dactyl://help/" + tag, uri); + if (tag in help.tags) + return RedirectChannel("dactyl://help/" + help.tags[tag] + "#" + tag.replace(/#/g, encodeURIComponent), uri); + }) + }); + }, + + Local: function Local(dactyl, modules, window) ({ + init: function init() { + dactyl.commands["dactyl.help"] = function (event) { + let elem = event.originalTarget; + help.help(elem.getAttribute("tag") || elem.textContent); + }; + }, + + /** + * 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 && Set.has(help.files, topic)) + return topic; + let items = modules.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; + }, + + /** + * 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" : modules.options["helpfile"]; + + if (Set.has(help.files, helpFile)) + 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" }); + } + + }), + + // Find the tags in the document. + addTags: function addTags(file, doc) { + for (let elem in DOM.XPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc)) + for (let tag in values((elem.value || elem.textContent).split(/\s+/))) + this.tags[tag] = file; + }, + + namespaces: ["locale-local", "locale"], + + // Find help and overlay files with the given name. + findHelpFile: function findHelpFile(file) { + let result = []; + for (let namespace in values(this.namespaces)) { + let url = ["dactyl://", namespace, "/", file, ".xml"].join(""); + let res = util.httpGet(url); + if (res) { + if (res.responseXML.documentElement.localName == "document") + this.files[file] = url; + if (res.responseXML.documentElement.localName == "overlay") + this.overlays[file] = url; + result.push(res.responseXML); + } + } + return result; + }, + + initialize: function initialize(force) { + // Waits for the add-on to become available, if necessary. + config.addon; + config.version; + + if (force || !this.initialized) { + + this.files["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() + ]; + } + + + + // 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. + this.tags["all"] = this.tags["all.xml"] = "all"; + let files = this.findHelpFile("all").map(function (doc) + [f.value for (f in DOM.XPath("//dactyl:include/@href", doc))]); + + // Scrape the tags from the rest of the help files. + array.flatten(files).forEach(function (file) { + this.tags[file + ".xml"] = file; + this.findHelpFile(file).forEach(function (doc) { + this.addTags(file, doc); + }, this); + }, this); + + this.tags["versions"] = this.tags["versions.xml"] = "versions"; + + this.addTags("versions", util.httpGet("dactyl://help/versions").responseXML); + + help.initialized = true; + } + }, +}, { +}, { + commands: function init_commands(dactyl, modules, window) { + const { commands, completion, help } = modules; + + [ + { + 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")); + help.help(args.literalArg, consolidated); + }, { + argCount: "?", + bang: true, + completer: function (context) completion.help(context, consolidated), + literal: 0 + }); + }); + }, + completion: function init_completion(dactyl, modules, window) { + const { completion } = modules; + + completion.help = function completion_help(context, consolidated) { + dactyl.initHelp(); + context.title = ["Help"]; + context.anchored = false; + context.completions = help.tags; + if (consolidated) + context.keys = { text: 0, description: function () "all" }; + }; + }, + mappings: function init_mappings(dactyl, modules, window) { + const { help, mappings, modes } = modules; + + mappings.add([modes.MAIN], ["<open-help>", "<F1>"], + "Open the introductory help page", + function () { help.help(); }); + + mappings.add([modes.MAIN], ["<open-single-help>", "<A-F1>"], + "Open the single, consolidated help page", + function () { modules.ex.helpall(); }); + } +}); + +endModule(); + +// vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/modules/io.jsm b/common/modules/io.jsm index aeead9b3..20bbc3d9 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -13,7 +13,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("io", { exports: ["IO", "io"], require: ["services"], - use: ["config", "messages", "storage", "styles", "template", "util"] + use: ["config", "help", "messages", "storage", "styles", "template", "util"] }, this); // TODO: why are we passing around strings rather than file objects? @@ -174,7 +174,7 @@ var IO = Module("io", { util.flushCache(); dactyl.loadScript(uri.spec, context); - dactyl.helpInitialized = false; + help.initialized = false; } catch (e) { if (e.fileName) diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm index 788c923d..10e20a8a 100644 --- a/common/modules/overlay.jsm +++ b/common/modules/overlay.jsm @@ -9,7 +9,7 @@ try { Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("overlay", { exports: ["ModuleBase", "overlay"], - require: ["config", "highlight", "io", "services", "util"] + require: ["config", "help", "highlight", "io", "services", "util"] }, this); /** @@ -41,8 +41,6 @@ var Overlay = Module("Overlay", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReferen util.addObserver(this); this.overlays = {}; - services["dactyl:"]; // Hack. Force module initialization. - config.loadStyles(); this.timeout(this.initialize); diff --git a/common/modules/protocol.jsm b/common/modules/protocol.jsm new file mode 100644 index 00000000..23b6801c --- /dev/null +++ b/common/modules/protocol.jsm @@ -0,0 +1,228 @@ +// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com> +// +// This work is licensed for reuse under an MIT license. Details are +// given in the LICENSE.txt file included with this file. +"use strict"; + +Components.utils.import("resource://dactyl/bootstrap.jsm"); +defineModule("protocol", { + exports: ["LocaleChannel", "Protocol", "RedirectChannel", "StringChannel", "XMLChannel"], + require: ["services", "util"] +}, this); + +var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].getService(Ci.nsIPrincipal); + +var DNE = "resource://gre/does/not/exist"; +var _DNE; + +function Channel(url, orig, noFake) { + try { + if (url == null) + return noFake ? null : FakeChannel(orig); + + if (url instanceof Ci.nsIChannel) + return url; + + if (typeof url === "function") + return let ([type, data] = url(orig)) StringChannel(data, type, orig); + + if (isArray(url)) + return let ([type, data] = url) StringChannel(data, type, orig); + + let uri = services.io.newURI(url, null, null); + return (new XMLChannel(uri, null, noFake)).channel; + } + catch (e) { + util.reportError(e); + throw e; + } +} +function FakeChannel(orig) { + let channel = services.io.newChannel(DNE, null, null); + channel.originalURI = orig; + return channel; +} +function RedirectChannel(to, orig, time) { + let html = <html><head><meta http-equiv="Refresh" content={(time || 0) + ";" + to}/></head></html>.toXMLString(); + return StringChannel(html, "text/html", services.io.newURI(to, null, null)); +} + +function Protocol(scheme, classID, contentBase) { + function Protocol() { + ProtocolBase.call(this); + } + Protocol.prototype = { + __proto__: ProtocolBase.prototype, + + classID: Components.ID(classID), + + scheme: scheme, + + contentBase: contentBase, + + _xpcom_factory: JSMLoader.Factory(Protocol), + }; + return Protocol; +} + +function ProtocolBase() { + this.wrappedJSObject = this; + + this.pages = {}; + this.providers = {}; +} +ProtocolBase.prototype = { + get contractID() "@mozilla.org/network/protocol;1?name=" + this.scheme, + get classDescription() this.scheme + " utility protocol", + QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]), + + defaultPort: -1, + allowPort: function (port, scheme) false, + protocolFlags: 0 + | Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE + | Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE, + + newURI: function newURI(spec, charset, baseURI) { + var uri = Cc["@mozilla.org/network/standard-url;1"] + .createInstance(Ci.nsIStandardURL) + .QueryInterface(Ci.nsIURI); + if (baseURI && baseURI.host === "data") + baseURI = null; + uri.init(uri.URLTYPE_STANDARD, this.defaultPort, spec, charset, baseURI); + return uri; + }, + + newChannel: function newChannel(uri) { + try { + uri.QueryInterface(Ci.nsIURL); + + if (uri.host in this.providers) + return Channel(this.providers[uri.host](uri, uri.filePath.substr(1)), uri); + + let path = decodeURIComponent(uri.path.replace(/^\/|#.*/g, "")); + switch(uri.host) { + case "content": + return Channel(this.pages[path] || this.contentBase + path, uri); + case "data": + try { + var channel = services.io.newChannel(uri.path.replace(/^\/(.*)(?:#.*)?/, "data:$1"), + null, null); + } + catch (e) { + var error = e; + break; + } + channel.contentCharset = "UTF-8"; + channel.owner = systemPrincipal; + channel.originalURI = uri; + return channel; + } + } + catch (e) { + util.reportError(e); + } + if (error) + throw error; + return FakeChannel(uri); + } +}; + +function LocaleChannel(pkg, locale, path, orig) { + for each (let locale in [locale, "en-US"]) + for each (let sep in "-/") { + var channel = Channel(["resource:/", pkg + sep + locale, path].join("/"), orig, true); + if (channel) + return channel; + } + + return FakeChannel(orig); +} + +function StringChannel(data, contentType, uri) { + let channel = services.StreamChannel(uri); + channel.contentStream = services.CharsetConv("UTF-8").convertToInputStream(data); + if (contentType) + channel.contentType = contentType; + channel.contentCharset = "UTF-8"; + channel.owner = systemPrincipal; + if (uri) + channel.originalURI = uri; + return channel; +} + +function XMLChannel(uri, contentType, noFake) { + try { + var channel = services.io.newChannelFromURI(uri); + var channelStream = channel.open(); + } + catch (e) { + this.channel = noFake ? null : FakeChannel(uri); + return; + } + + this.uri = uri; + this.sourceChannel = services.io.newChannelFromURI(uri); + this.pipe = services.Pipe(true, true, 0, 0, null); + this.writes = []; + + this.channel = services.StreamChannel(uri); + this.channel.contentStream = this.pipe.inputStream; + this.channel.contentType = contentType || channel.contentType; + this.channel.contentCharset = "UTF-8"; + this.channel.owner = systemPrincipal; + + let stream = services.InputStream(channelStream); + let [, pre, doctype, url, open, post] = util.regexp(<![CDATA[ + ^ ([^]*?) + (?: + (<!DOCTYPE \s+ \S+ \s+) SYSTEM \s+ "([^"]*)" + (\s+ \[)? + ([^]*) + )? + $ + ]]>, "x").exec(stream.read(4096)); + this.writes.push(pre); + if (doctype) { + this.writes.push(doctype + "[\n"); + try { + this.writes.push(services.io.newChannel(url, null, null).open()); + } + catch (e) {} + if (!open) + this.writes.push("\n]"); + this.writes.push(post); + } + this.writes.push(channelStream); + + this.writeNext(); +} +XMLChannel.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver]), + writeNext: function () { + try { + if (!this.writes.length) + this.pipe.outputStream.close(); + else { + let stream = this.writes.shift(); + if (isString(stream)) + stream = services.StringStream(stream); + + services.StreamCopier(stream, this.pipe.outputStream, null, + false, true, 4096, true, false) + .asyncCopy(this, null); + } + } + catch (e) { + util.reportError(e); + } + }, + + onStartRequest: function (request, context) {}, + onStopRequest: function (request, context, statusCode) { + this.writeNext(); + } +}; + +endModule(); + +// vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/modules/sanitizer.jsm b/common/modules/sanitizer.jsm index 886121d1..0d602110 100644 --- a/common/modules/sanitizer.jsm +++ b/common/modules/sanitizer.jsm @@ -656,7 +656,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef options.add(["cookies", "ck"], "The default mode for newly added cookie permissions", "stringlist", "session", - { get values() iter(Sanitizer.COMMANDS) }); + { get values() Sanitizer.COMMANDS }); options.add(["cookieaccept", "ca"], "When to accept cookies", diff --git a/common/modules/services.jsm b/common/modules/services.jsm index 707619f2..d699d699 100644 --- a/common/modules/services.jsm +++ b/common/modules/services.jsm @@ -75,6 +75,7 @@ var Services = Module("Services", { this.addClass("Find", "@mozilla.org/embedcomp/rangefind;1", "nsIFind"); this.addClass("HtmlConverter","@mozilla.org/widget/htmlformatconverter;1", "nsIFormatConverter"); this.addClass("HtmlEncoder", "@mozilla.org/layout/htmlCopyEncoder;1", "nsIDocumentEncoder"); + this.addClass("InterfacePointer", "@mozilla.org/supports-interface-pointer;1", "nsISupportsInterfacePointer", "data"); this.addClass("InputStream", "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"); this.addClass("Persist", "@mozilla.org/embedding/browser/nsWebBrowserPersist;1", "nsIWebBrowserPersist"); this.addClass("Pipe", "@mozilla.org/pipe;1", "nsIPipe", "init"); diff --git a/common/modules/template.jsm b/common/modules/template.jsm index 17ccb9df..9fb99f3e 100644 --- a/common/modules/template.jsm +++ b/common/modules/template.jsm @@ -8,7 +8,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("template", { exports: ["Binding", "Template", "template"], require: ["util"], - use: ["messages", "services"] + use: ["help", "messages", "services"] }, this); default xml namespace = XHTML; @@ -204,7 +204,7 @@ var Template = Module("Template", { }, helpLink: function (token, text, type) { - if (!services["dactyl:"].initialized) + if (!help.initialized) util.dactyl.initHelp(); let topic = token; // FIXME: Evil duplication! @@ -213,7 +213,7 @@ var Template = Module("Template", { else if (/^n_/.test(topic)) topic = topic.slice(2); - if (services["dactyl:"].initialized && !Set.has(services["dactyl:"].HELP_TAGS, topic)) + if (help.initialized && !Set.has(help.tags, topic)) return <span highlight={type || ""}>{text || token}</span>; XML.ignoreWhitespace = false; XML.prettyPrinting = false; @@ -224,7 +224,7 @@ var Template = Module("Template", { return <a highlight={"InlineHelpLink " + type} tag={topic} href={"dactyl://help-tag/" + topic} dactyl:command="dactyl.help" xmlns:dactyl={NS}>{text || topic}</a>; }, HelpLink: function (token) { - if (!services["dactyl:"].initialized) + if (!help.initialized) util.dactyl.initHelp(); let topic = token; // FIXME: Evil duplication! @@ -233,7 +233,7 @@ var Template = Module("Template", { else if (/^n_/.test(topic)) topic = topic.slice(2); - if (services["dactyl:"].initialized && !Set.has(services["dactyl:"].HELP_TAGS, topic)) + if (help.initialized && !Set.has(help.tags, topic)) return <>{token}</>; XML.ignoreWhitespace = false; XML.prettyPrinting = false; |