mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2025-12-20 07:07:59 +01:00
Replace XPath-based hint paths with CSS selectors. Needs cleanup and validation.
--HG-- extra : rebase_source : 83035481bf697b7b57e17e516b0dfc61329164c6
This commit is contained in:
@@ -1382,6 +1382,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
|
|||||||
dactyl.echoerr(template.linkifyHelp(error.message));
|
dactyl.echoerr(template.linkifyHelp(error.message));
|
||||||
else
|
else
|
||||||
dactyl.beep();
|
dactyl.beep();
|
||||||
|
|
||||||
|
if (!error.noTrace)
|
||||||
|
util.reportError(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (error.result == Cr.NS_BINDING_ABORTED)
|
if (error.result == Cr.NS_BINDING_ABORTED)
|
||||||
|
|||||||
@@ -231,7 +231,9 @@
|
|||||||
|
|
||||||
<xsl:template match="dactyl:default[not(@type='plain')]" mode="help-2">
|
<xsl:template match="dactyl:default[not(@type='plain')]" mode="help-2">
|
||||||
<xsl:variable name="type" select="preceding-sibling::dactyl:type[1] | following-sibling::dactyl:type[1]"/>
|
<xsl:variable name="type" select="preceding-sibling::dactyl:type[1] | following-sibling::dactyl:type[1]"/>
|
||||||
<span dactyl:highlight="HelpDefault">(default:<xsl:text> </xsl:text>
|
<span dactyl:highlight="HelpDefault">
|
||||||
|
<xsl:copy-of select="@*"/>
|
||||||
|
<xsl:text>(default: </xsl:text>
|
||||||
<xsl:choose>
|
<xsl:choose>
|
||||||
<xsl:when test="$type = 'string'">
|
<xsl:when test="$type = 'string'">
|
||||||
<span dactyl:highlight="HelpString" delim="'"><xsl:apply-templates mode="help-1"/></span>
|
<span dactyl:highlight="HelpString" delim="'"><xsl:apply-templates mode="help-1"/></span>
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ var HintSession = Class("HintSession", CommandMode, {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = doc.body || util.evaluateXPath(["body"], doc).snapshotItem(0);
|
let body = doc.body || doc.querySelector("body");
|
||||||
if (body) {
|
if (body) {
|
||||||
let fragment = util.xmlToDom(<div highlight="hints"/>, doc);
|
let fragment = util.xmlToDom(<div highlight="hints"/>, doc);
|
||||||
body.appendChild(fragment);
|
body.appendChild(fragment);
|
||||||
@@ -281,7 +281,7 @@ var HintSession = Class("HintSession", CommandMode, {
|
|||||||
let baseNodeAbsolute = util.xmlToDom(<span highlight="Hint" style="display: none"/>, doc);
|
let baseNodeAbsolute = util.xmlToDom(<span highlight="Hint" style="display: none"/>, doc);
|
||||||
|
|
||||||
let mode = this.hintMode;
|
let mode = this.hintMode;
|
||||||
let res = util.evaluateXPath(mode.xpath, doc, true);
|
let res = mode.matcher(doc);
|
||||||
|
|
||||||
let start = this.pageHints.length;
|
let start = this.pageHints.length;
|
||||||
for (let elem in res) {
|
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", {
|
var Hints = Module("hints", {
|
||||||
init: function init() {
|
init: function init() {
|
||||||
this.resizeTimer = Timer(100, 500, function () {
|
this.resizeTimer = Timer(100, 500, function () {
|
||||||
@@ -682,8 +705,8 @@ var Hints = Module("hints", {
|
|||||||
events.listen(appContent, "scroll", this.resizeTimer.closure.tell, false);
|
events.listen(appContent, "scroll", this.resizeTimer.closure.tell, false);
|
||||||
|
|
||||||
const Mode = Hints.Mode;
|
const Mode = Hints.Mode;
|
||||||
Mode.defaultValue("tags", function () function () options["hinttags"]);
|
Mode.defaultValue("tags", function () function () options.get("hinttags").matcher);
|
||||||
Mode.prototype.__defineGetter__("xpath", function ()
|
Mode.prototype.__defineGetter__("matcher", function ()
|
||||||
options.get("extendedhinttags").getKey(this.name, this.tags()));
|
options.get("extendedhinttags").getKey(this.name, this.tags()));
|
||||||
|
|
||||||
this.modes = {};
|
this.modes = {};
|
||||||
@@ -1162,23 +1185,41 @@ var Hints = Module("hints", {
|
|||||||
},
|
},
|
||||||
options: function () {
|
options: function () {
|
||||||
function xpath(arg) util.makeXPath(arg);
|
function xpath(arg) util.makeXPath(arg);
|
||||||
|
|
||||||
options.add(["extendedhinttags", "eht"],
|
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", {
|
"regexpmap", {
|
||||||
"[iI]": xpath(["img"]),
|
"[iI]": "img",
|
||||||
"[asOTivVWy]": xpath(["{a,area}[@href]", "{img,iframe}[@src]"]),
|
"[asOTivVWy]": ["a[href]", "area[href]", "img[src]", "iframe[src]"],
|
||||||
"[f]": xpath(["body"]),
|
"[f]": "body",
|
||||||
"[F]": xpath(["body", "code", "div", "html", "p", "pre", "span"]),
|
"[F]": ["body", "code", "div", "html", "p", "pre", "span"],
|
||||||
"[S]": xpath(["input[not(@type='hidden')]", "textarea", "button", "select"])
|
"[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"],
|
options.add(["hinttags", "ht"],
|
||||||
"XPath string of hintable elements activated by 'f' and 'F'",
|
"XPath string of hintable elements activated by 'f' and 'F'",
|
||||||
"string", xpath(["input[not(@type='hidden')]", "a", "area", "iframe", "textarea", "button", "select",
|
"stringlist", "input:not([type=hidden]),a,area,iframe,textarea,button,select," +
|
||||||
"*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
|
"[onclick],[onmouseover],[onmousedown],[onmouseup],[oncommand]," +
|
||||||
"@tabindex or @role='link' or @role='button']"]),
|
"[tabindex],[role=link],[role=button]",
|
||||||
{ validator: Option.validateXPath });
|
{
|
||||||
|
setter: function (values) {
|
||||||
|
this.matcher = compileMatcher(values);
|
||||||
|
return values;
|
||||||
|
},
|
||||||
|
validator: Option.validateXPath
|
||||||
|
});
|
||||||
|
|
||||||
options.add(["hintkeys", "hk"],
|
options.add(["hintkeys", "hk"],
|
||||||
"The keys used to label and select hints",
|
"The keys used to label and select hints",
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>
|
<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>
|
||||||
|
|
||||||
<!DOCTYPE document SYSTEM "dactyl://content/dtd" [
|
<!DOCTYPE document SYSTEM "dactyl://content/dtd">
|
||||||
<!ENTITY hinttags "//*[@onclick or @onmouseover or @onmousedown or
|
|
||||||
@onmouseup or @oncommand or @role='link'] |
|
|
||||||
//input[not(@type='hidden')] | //xhtml:input[not(@type='hidden')] |
|
|
||||||
//a | //xhtml:a | //area | //xhtml:area |
|
|
||||||
//button | //xhtml:button | //iframe | //xhtml:iframe |
|
|
||||||
//select | //xhtml:select | //textarea | //xhtml:textarea">
|
|
||||||
]>
|
|
||||||
|
|
||||||
<document
|
<document
|
||||||
name="options"
|
name="options"
|
||||||
@@ -631,26 +624,19 @@
|
|||||||
|
|
||||||
<item>
|
<item>
|
||||||
<tags>'eht' 'extendedhinttags'</tags>
|
<tags>'eht' 'extendedhinttags'</tags>
|
||||||
<spec>'extendedhinttags' 'eht'</spec>
|
|
||||||
<strut/>
|
<strut/>
|
||||||
|
<spec>'extendedhinttags'</spec>
|
||||||
<type>regexpmap</type>
|
<type>regexpmap</type>
|
||||||
<default>[iI]:'//img | //xhtml:img',
|
<default style="display: block;">[asOTivVWy]:a[href],area[href],img[src],iframe[src],
|
||||||
[OTivVWy]:'//a[@href] | //xhtml:a[@href] |
|
[f]:body,
|
||||||
//area[@href] | //xhtml:area[@href] |
|
[F]:body,code,div,html,p,pre,span,
|
||||||
//img[@src] | //xhtml:img[@src] |
|
[iI]:img,
|
||||||
//iframe[@src] | //xhtml:iframe[@src]',
|
[S]:button,'input:not([type=hidden])',select,textarea</default>
|
||||||
[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'</default>
|
|
||||||
<description>
|
<description>
|
||||||
<p>
|
<p>
|
||||||
Defines specialized XPath expressions for arbitrary
|
Defines specialized CSS selectors or XPath expressions for arbitrary
|
||||||
<t>extended-hints</t> modes. If no matches are found, the value of
|
<t>extended-hints</t> modes. The syntax is the same as for
|
||||||
|
<o>hinttags</o>. If no matches are found, the value of
|
||||||
<o>hinttags</o> is used.
|
<o>hinttags</o> is used.
|
||||||
</p>
|
</p>
|
||||||
</description>
|
</description>
|
||||||
@@ -855,12 +841,16 @@
|
|||||||
<tags>'ht' 'hinttags'</tags>
|
<tags>'ht' 'hinttags'</tags>
|
||||||
<strut/>
|
<strut/>
|
||||||
<spec>'hinttags' 'ht'</spec>
|
<spec>'hinttags' 'ht'</spec>
|
||||||
<type>string</type>
|
<type>stringlist</type>
|
||||||
<default>&hinttags;</default>
|
<default style="display: block;">a,area,button,iframe,input:not([type=hidden]),select,textarea,
|
||||||
|
[onclick],[onmouseover],[onmousedown],[onmouseup],[oncommand],
|
||||||
|
[tabindex],[role=link],[role=button]</default>
|
||||||
<description>
|
<description>
|
||||||
<p>
|
<p>
|
||||||
The XPath string used to select elements for
|
A list of CSS selectors or XPath expressions used to select elements
|
||||||
<link topic="hints">hinting</link>. Can be overridden for
|
for <link topic="hints">hinting</link>. Values beginning with the
|
||||||
|
string <str>xpath:</str> are treated as XPath expressions, while any
|
||||||
|
other values are treated as CSS selectors. Can be overridden for
|
||||||
individual <t>extended-hints</t> modes with the
|
individual <t>extended-hints</t> modes with the
|
||||||
<o>extendedhinttags</o> option.
|
<o>extendedhinttags</o> option.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -205,8 +205,7 @@ var IO = Module("io", {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (!(e instanceof FailedAssertion))
|
dactyl.reportError(e);
|
||||||
dactyl.reportError(e);
|
|
||||||
let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e);
|
let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e);
|
||||||
if (!params.silent)
|
if (!params.silent)
|
||||||
dactyl.echoerr(message);
|
dactyl.echoerr(message);
|
||||||
|
|||||||
@@ -415,18 +415,23 @@ var Option = Class("Option", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
parseRegexp: function (value, result, flags) {
|
parseRegexp: function (value, result, flags) {
|
||||||
|
let keepQuotes = this && this.keepQuotes;
|
||||||
if (isArray(flags)) // Called by Array.map
|
if (isArray(flags)) // Called by Array.map
|
||||||
result = flags = undefined;
|
result = flags = undefined;
|
||||||
|
|
||||||
|
if (flags == null)
|
||||||
|
flags = this && this.regexpFlags || "";
|
||||||
|
|
||||||
let [, bang, val] = /^(!?)(.*)/.exec(value);
|
let [, bang, val] = /^(!?)(.*)/.exec(value);
|
||||||
let re = RegExp(Option.dequote(val), flags);
|
let re = RegExp(Option.dequote(val), flags);
|
||||||
re.bang = bang;
|
re.bang = bang;
|
||||||
re.result = result !== undefined ? result : !bang;
|
re.result = result !== undefined ? result : !bang;
|
||||||
re.toString = function () Option.unparseRegexp(this);
|
re.toString = function () Option.unparseRegexp(this, keepQuotes);
|
||||||
return re;
|
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) {
|
parseSite: function parseSite(pattern, result, rest) {
|
||||||
if (isArray(rest)) // Called by Array.map
|
if (isArray(rest)) // Called by Array.map
|
||||||
@@ -500,23 +505,25 @@ var Option = Class("Option", {
|
|||||||
return [key, Option.dequote(v.substr(count + 1))];
|
return [key, Option.dequote(v.substr(count + 1))];
|
||||||
})),
|
})),
|
||||||
|
|
||||||
regexpmap: function (value)
|
regexpmap: function (value) Option.parse.list.call(this, value, Option.parseRegexp),
|
||||||
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),
|
|
||||||
|
|
||||||
sitemap: function (value)
|
sitemap: function (value) Option.parse.list.call(this, value, Option.parseSite),
|
||||||
Option.splitList(value, true).map(function (v) {
|
|
||||||
let [count, re, quote] = Commands.parseArg(v, /:/, true);
|
list: function (value, parse) let (prev = null)
|
||||||
let val = Option.dequote(v.substr(count + 1));
|
array.compact(Option.splitList(value, true).map(function (v) {
|
||||||
if (count === v.length)
|
let [count, filter, quote] = Commands.parseArg(v, /:/, true);
|
||||||
[val, re] = [re, "*"];
|
|
||||||
return Option.parseSite(re, val);
|
let val = v.substr(count + 1);
|
||||||
}, this)
|
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: {
|
testValues: {
|
||||||
@@ -549,7 +556,7 @@ var Option = Class("Option", {
|
|||||||
return res;
|
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)
|
Commands.quoteArg[/[\s|"'\\,]|^$/.test(str) || re && re.test && re.test(str)
|
||||||
? (/[\b\f\n\r\t]/.test(str) ? '"' : "'")
|
? (/[\b\f\n\r\t]/.test(str) ? '"' : "'")
|
||||||
: ""](str, re),
|
: ""](str, re),
|
||||||
@@ -683,6 +690,7 @@ var Option = Class("Option", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
validateXPath: function (values) {
|
validateXPath: function (values) {
|
||||||
|
return true; // For now.
|
||||||
let evaluator = services.XPathEvaluator();
|
let evaluator = services.XPathEvaluator();
|
||||||
return this.testValues(values,
|
return this.testValues(values,
|
||||||
function (value) evaluator.createExpression(value, util.evaluateXPath.resolver));
|
function (value) evaluator.createExpression(value, util.evaluateXPath.resolver));
|
||||||
|
|||||||
@@ -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");
|
var NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator");
|
||||||
default xml namespace = XHTML;
|
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 Point = Struct("x", "y");
|
||||||
|
|
||||||
var wrapCallback = function wrapCallback(fn) {
|
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
|
* @param {string} message The message to present to the
|
||||||
* user on failure.
|
* user on failure.
|
||||||
*/
|
*/
|
||||||
assert: function (condition, message) {
|
assert: function (condition, message, quiet) {
|
||||||
if (!condition)
|
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 () []),
|
errors: Class.memoize(function () []),
|
||||||
maxErrors: 15,
|
maxErrors: 15,
|
||||||
reportError: function (error) {
|
reportError: function (error) {
|
||||||
|
if (error.noTrace)
|
||||||
|
return;
|
||||||
|
|
||||||
if (Cu.reportError)
|
if (Cu.reportError)
|
||||||
Cu.reportError(error);
|
Cu.reportError(error);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user