diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 763e07d5..cfca1372 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -1382,6 +1382,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { dactyl.echoerr(template.linkifyHelp(error.message)); else dactyl.beep(); + + if (!error.noTrace) + util.reportError(error); return; } if (error.result == Cr.NS_BINDING_ABORTED) diff --git a/common/content/help.xsl b/common/content/help.xsl index ac166064..0f5ae9b5 100644 --- a/common/content/help.xsl +++ b/common/content/help.xsl @@ -231,7 +231,9 @@ - (default: + + + (default: diff --git a/common/content/hints.js b/common/content/hints.js index a473e6c9..9538f4fe 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -271,7 +271,7 @@ var HintSession = Class("HintSession", CommandMode, { return true; } - let body = doc.body || util.evaluateXPath(["body"], doc).snapshotItem(0); + let body = doc.body || doc.querySelector("body"); if (body) { let fragment = util.xmlToDom(
, doc); body.appendChild(fragment); @@ -281,7 +281,7 @@ var HintSession = Class("HintSession", CommandMode, { let baseNodeAbsolute = util.xmlToDom(, doc); let mode = this.hintMode; - let res = util.evaluateXPath(mode.xpath, doc, true); + let res = mode.matcher(doc); let start = this.pageHints.length; for (let elem in res) { @@ -670,6 +670,29 @@ var HintSession = Class("HintSession", CommandMode, { }, }); +function compileMatcher(list) { + let xpath = [], css = []; + for (let elem in values(list)) + if (/^xpath:/.test(elem)) + xpath.push(elem.substr(6)); + else + css.push(elem); + + return update( + function matcher(node) { + if (matcher.xpath) + for (let elem in util.evaluateXPath(matcher.xpath, node)) + yield elem; + + if (matcher.css) + for (let [, elem] in iter(node.querySelectorAll(matcher.css))) + yield elem; + }, { + css: css.join(", "), + xpath: xpath.join(" | ") + }); +} + var Hints = Module("hints", { init: function init() { this.resizeTimer = Timer(100, 500, function () { @@ -682,8 +705,8 @@ var Hints = Module("hints", { events.listen(appContent, "scroll", this.resizeTimer.closure.tell, false); const Mode = Hints.Mode; - Mode.defaultValue("tags", function () function () options["hinttags"]); - Mode.prototype.__defineGetter__("xpath", function () + Mode.defaultValue("tags", function () function () options.get("hinttags").matcher); + Mode.prototype.__defineGetter__("matcher", function () options.get("extendedhinttags").getKey(this.name, this.tags())); this.modes = {}; @@ -1162,23 +1185,41 @@ var Hints = Module("hints", { }, options: function () { function xpath(arg) util.makeXPath(arg); + options.add(["extendedhinttags", "eht"], - "XPath strings of hintable elements for extended hint modes", + "XPath or CSS selector strings of hintable elements for extended hint modes", "regexpmap", { - "[iI]": xpath(["img"]), - "[asOTivVWy]": xpath(["{a,area}[@href]", "{img,iframe}[@src]"]), - "[f]": xpath(["body"]), - "[F]": xpath(["body", "code", "div", "html", "p", "pre", "span"]), - "[S]": xpath(["input[not(@type='hidden')]", "textarea", "button", "select"]) + "[iI]": "img", + "[asOTivVWy]": ["a[href]", "area[href]", "img[src]", "iframe[src]"], + "[f]": "body", + "[F]": ["body", "code", "div", "html", "p", "pre", "span"], + "[S]": ["input:not([type=hidden])", "textarea", "button", "select"] }, - { validator: Option.validateXPath }); + { + keepQuotes: true, + getKey: function (val, default_) + let (res = array.nth(this.value, function (re) re.test(val), 0)) + res ? res.matcher : default_, + setter: function (vals) { + for (let value in values(vals)) + value.matcher = compileMatcher(Option.splitList(value.result)); + return vals; + }, + validator: Option.validateXPath + }); options.add(["hinttags", "ht"], "XPath string of hintable elements activated by 'f' and 'F'", - "string", xpath(["input[not(@type='hidden')]", "a", "area", "iframe", "textarea", "button", "select", - "*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " + - "@tabindex or @role='link' or @role='button']"]), - { validator: Option.validateXPath }); + "stringlist", "input:not([type=hidden]),a,area,iframe,textarea,button,select," + + "[onclick],[onmouseover],[onmousedown],[onmouseup],[oncommand]," + + "[tabindex],[role=link],[role=button]", + { + setter: function (values) { + this.matcher = compileMatcher(values); + return values; + }, + validator: Option.validateXPath + }); options.add(["hintkeys", "hk"], "The keys used to label and select hints", diff --git a/common/locale/en-US/options.xml b/common/locale/en-US/options.xml index 5e98816e..9247a5c2 100644 --- a/common/locale/en-US/options.xml +++ b/common/locale/en-US/options.xml @@ -1,14 +1,7 @@ - -]> + 'eht' 'extendedhinttags' - 'extendedhinttags' 'eht' + 'extendedhinttags' regexpmap - [iI]:'//img | //xhtml:img', - [OTivVWy]:'//a[@href] | //xhtml:a[@href] | - //area[@href] | //xhtml:area[@href] | - //img[@src] | //xhtml:img[@src] | - //iframe[@src] | //xhtml:iframe[@src]', - [F]:'//div | //xhtml:div | //span | //xhtml:span | - //p | //xhtml:p | //body | //xhtml:body | - //html | //xhtml:html' - [S]:'//input[not(@type=''hidden'')] | - //xhtml:input[not(@type=''hidden'')] | - //textarea | //xhtml:textarea | - //button | //xhtml:button | - //select | //xhtml:select' + [asOTivVWy]:a[href],area[href],img[src],iframe[src], + [f]:body, + [F]:body,code,div,html,p,pre,span, + [iI]:img, + [S]:button,'input:not([type=hidden])',select,textarea

- Defines specialized XPath expressions for arbitrary - extended-hints modes. If no matches are found, the value of + Defines specialized CSS selectors or XPath expressions for arbitrary + extended-hints modes. The syntax is the same as for + hinttags. If no matches are found, the value of hinttags is used.

@@ -855,12 +841,16 @@ 'ht' 'hinttags' 'hinttags' 'ht' - string - &hinttags; + stringlist + a,area,button,iframe,input:not([type=hidden]),select,textarea, + [onclick],[onmouseover],[onmousedown],[onmouseup],[oncommand], + [tabindex],[role=link],[role=button]

- The XPath string used to select elements for - hinting. Can be overridden for + A list of CSS selectors or XPath expressions used to select elements + for hinting. Values beginning with the + string xpath: are treated as XPath expressions, while any + other values are treated as CSS selectors. Can be overridden for individual extended-hints modes with the extendedhinttags option.

diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 71c72ba2..ef18759f 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -205,8 +205,7 @@ var IO = Module("io", { return context; } catch (e) { - if (!(e instanceof FailedAssertion)) - dactyl.reportError(e); + dactyl.reportError(e); let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e); if (!params.silent) dactyl.echoerr(message); diff --git a/common/modules/options.jsm b/common/modules/options.jsm index 74e1347f..7a9452fc 100644 --- a/common/modules/options.jsm +++ b/common/modules/options.jsm @@ -415,18 +415,23 @@ var Option = Class("Option", { }, parseRegexp: function (value, result, flags) { + let keepQuotes = this && this.keepQuotes; if (isArray(flags)) // Called by Array.map result = flags = undefined; + if (flags == null) + flags = this && this.regexpFlags || ""; + let [, bang, val] = /^(!?)(.*)/.exec(value); let re = RegExp(Option.dequote(val), flags); re.bang = bang; re.result = result !== undefined ? result : !bang; - re.toString = function () Option.unparseRegexp(this); + re.toString = function () Option.unparseRegexp(this, keepQuotes); return re; }, - unparseRegexp: function (re) re.bang + Option.quote(util.regexp.getSource(re), /^!|:/) + - (typeof re.result === "boolean" ? "" : ":" + Option.quote(re.result)), + + unparseRegexp: function (re, quoted) re.bang + Option.quote(util.regexp.getSource(re), /^!|:/) + + (typeof re.result === "boolean" ? "" : ":" + (quoted ? re.result : Option.quote(re.result))), parseSite: function parseSite(pattern, result, rest) { if (isArray(rest)) // Called by Array.map @@ -500,23 +505,25 @@ var Option = Class("Option", { return [key, Option.dequote(v.substr(count + 1))]; })), - regexpmap: function (value) - Option.splitList(value, true).map(function (v) { - let [count, re, quote] = Commands.parseArg(v, /:/, true); - let val = Option.dequote(v.substr(count + 1)); - if (count === v.length) - [val, re] = [re, ".?"]; - return Option.parseRegexp(re, val, this.regexpFlags); - }, this), + regexpmap: function (value) Option.parse.list.call(this, value, Option.parseRegexp), - sitemap: function (value) - Option.splitList(value, true).map(function (v) { - let [count, re, quote] = Commands.parseArg(v, /:/, true); - let val = Option.dequote(v.substr(count + 1)); - if (count === v.length) - [val, re] = [re, "*"]; - return Option.parseSite(re, val); - }, this) + sitemap: function (value) Option.parse.list.call(this, value, Option.parseSite), + + list: function (value, parse) let (prev = null) + array.compact(Option.splitList(value, true).map(function (v) { + let [count, filter, quote] = Commands.parseArg(v, /:/, true); + + let val = v.substr(count + 1); + if (!this.keepQuotes) + val = Option.dequote(val); + + if (v.length > count) + return prev = parse.call(this, filter, val); + else { + util.assert(prev, "Syntax error", false); + prev.result += "," + v; + } + }, this)) }, testValues: { @@ -549,7 +556,7 @@ var Option = Class("Option", { return res; }, - quote: function quote(str, re) + quote: function quote(str, re) isArray(str) ? str.map(function (s) quote(s, re)).join(",") : Commands.quoteArg[/[\s|"'\\,]|^$/.test(str) || re && re.test && re.test(str) ? (/[\b\f\n\r\t]/.test(str) ? '"' : "'") : ""](str, re), @@ -683,6 +690,7 @@ var Option = Class("Option", { }, validateXPath: function (values) { + return true; // For now. let evaluator = services.XPathEvaluator(); return this.testValues(values, function (value) evaluator.createExpression(value, util.evaluateXPath.resolver)); diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 32fe8af7..d41024b6 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -22,7 +22,18 @@ var XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is var NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator"); default xml namespace = XHTML; -var FailedAssertion = Class("FailedAssertion", ErrorBase); +var FailedAssertion = Class("FailedAssertion", ErrorBase, { + init: function init(message, level, noTrace) { + if (noTrace !== undefined) + this.noTrace = noTrace; + init.supercall(this, message, level); + }, + + level: 3, + + noTrace: true +}); + var Point = Struct("x", "y"); var wrapCallback = function wrapCallback(fn) { @@ -144,9 +155,9 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * @param {string} message The message to present to the * user on failure. */ - assert: function (condition, message) { + assert: function (condition, message, quiet) { if (!condition) - throw FailedAssertion(message, 1); + throw FailedAssertion(message, 1, quiet === undefined ? true : quiet); }, /** @@ -1417,6 +1428,9 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), errors: Class.memoize(function () []), maxErrors: 15, reportError: function (error) { + if (error.noTrace) + return; + if (Cu.reportError) Cu.reportError(error);