Merge remote-tracking branch 'origin/pr/3806' into test

This commit is contained in:
Florian Bruhin 2018-10-08 18:09:58 +02:00
commit 6d4c8f5b13
9 changed files with 237 additions and 70 deletions

View File

@ -207,6 +207,7 @@
|<<hints.next_regexes,hints.next_regexes>>|Comma-separated list of regular expressions to use for 'next' links. |<<hints.next_regexes,hints.next_regexes>>|Comma-separated list of regular expressions to use for 'next' links.
|<<hints.prev_regexes,hints.prev_regexes>>|Comma-separated list of regular expressions to use for 'prev' links. |<<hints.prev_regexes,hints.prev_regexes>>|Comma-separated list of regular expressions to use for 'prev' links.
|<<hints.scatter,hints.scatter>>|Scatter hint key chains (like Vimium) or not (like dwb). |<<hints.scatter,hints.scatter>>|Scatter hint key chains (like Vimium) or not (like dwb).
|<<hints.selectors,hints.selectors>>|CSS selectors used to determine which elements on a page should have hints.
|<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase. |<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase.
|<<history_gap_interval,history_gap_interval>>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session. |<<history_gap_interval,history_gap_interval>>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session.
|<<input.escape_quits_reporter,input.escape_quits_reporter>>|Allow Escape to quit the crash reporter. |<<input.escape_quits_reporter,input.escape_quits_reporter>>|Allow Escape to quit the crash reporter.
@ -2574,6 +2575,73 @@ Type: <<types,Bool>>
Default: +pass:[true]+ Default: +pass:[true]+
[[hints.selectors]]
=== hints.selectors
CSS selectors used to determine which elements on a page should have hints.
This setting supports URL patterns.
Type: <<types,Dict>>
Default:
- +pass:[all]+:
* +pass:[a]+
* +pass:[area]+
* +pass:[textarea]+
* +pass:[select]+
* +pass:[input:not([type=&quot;hidden&quot;])]+
* +pass:[button]+
* +pass:[frame]+
* +pass:[iframe]+
* +pass:[img]+
* +pass:[link]+
* +pass:[summary]+
* +pass:[[onclick]]+
* +pass:[[onmousedown]]+
* +pass:[[role=&quot;link&quot;]]+
* +pass:[[role=&quot;option&quot;]]+
* +pass:[[role=&quot;button&quot;]]+
* +pass:[[ng-click]]+
* +pass:[[ngClick]]+
* +pass:[[data-ng-click]]+
* +pass:[[x-ng-click]]+
- +pass:[images]+:
* +pass:[img]+
- +pass:[inputs]+:
* +pass:[input[type=&quot;text&quot;]]+
* +pass:[input[type=&quot;date&quot;]]+
* +pass:[input[type=&quot;datetime-local&quot;]]+
* +pass:[input[type=&quot;email&quot;]]+
* +pass:[input[type=&quot;month&quot;]]+
* +pass:[input[type=&quot;number&quot;]]+
* +pass:[input[type=&quot;password&quot;]]+
* +pass:[input[type=&quot;search&quot;]]+
* +pass:[input[type=&quot;tel&quot;]]+
* +pass:[input[type=&quot;time&quot;]]+
* +pass:[input[type=&quot;url&quot;]]+
* +pass:[input[type=&quot;week&quot;]]+
* +pass:[input:not([type])]+
* +pass:[textarea]+
- +pass:[links]+:
* +pass:[a[href]]+
* +pass:[area[href]]+
* +pass:[link[href]]+
* +pass:[[role=&quot;link&quot;][href]]+
- +pass:[media]+:
* +pass:[audio]+
* +pass:[img]+
* +pass:[video]+
- +pass:[url]+:
* +pass:[[src]]+
* +pass:[[href]]+
[[hints.uppercase]] [[hints.uppercase]]
=== hints.uppercase === hints.uppercase
Make characters in hint strings uppercase. Make characters in hint strings uppercase.

View File

@ -637,9 +637,8 @@ class HintManager(QObject):
star_args_optional=True, maxsplit=2) star_args_optional=True, maxsplit=2)
@cmdutils.argument('win_id', win_id=True) @cmdutils.argument('win_id', win_id=True)
def start(self, # pylint: disable=keyword-arg-before-vararg def start(self, # pylint: disable=keyword-arg-before-vararg
group=webelem.Group.all, target=Target.normal, group='all', target=Target.normal, *args, win_id, mode=None,
*args, win_id, mode=None, add_history=False, rapid=False, add_history=False, rapid=False, first=False):
first=False):
"""Start hinting. """Start hinting.
Args: Args:
@ -747,10 +746,25 @@ class HintManager(QObject):
raise cmdexc.CommandError("No URL set for this page yet!") raise cmdexc.CommandError("No URL set for this page yet!")
self._context.args = args self._context.args = args
self._context.group = group self._context.group = group
selector = webelem.SELECTORS[self._context.group] selector = self._get_selector()
self._context.tab.elements.find_css(selector, self._start_cb, self._context.tab.elements.find_css(selector, self._start_cb,
only_visible=True) only_visible=True)
def _get_selector(self):
"""Get the CSS selectors for this url and hinting group."""
url = self._context.baseurl
group = self._context.group
selectors = config.instance.get('hints.selectors', url)
if group not in selectors:
selectors = config.val.hints.selectors
if group not in selectors:
raise cmdexc.CommandError("Undefined hinting group "
"'{}'!".format(group))
return ','.join(selectors[group])
def current_mode(self): def current_mode(self):
"""Return the currently active hinting mode (or None otherwise).""" """Return the currently active hinting mode (or None otherwise)."""
if self._context is None: if self._context is None:

View File

@ -21,7 +21,6 @@
import posixpath import posixpath
from qutebrowser.browser import webelem
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.utils import objreg, urlutils, log, message, qtutils from qutebrowser.utils import objreg, urlutils, log, message, qtutils
from qutebrowser.mainwindow import mainwindow from qutebrowser.mainwindow import mainwindow
@ -147,5 +146,6 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
else: else:
browsertab.openurl(url) browsertab.openurl(url)
browsertab.elements.find_css(webelem.SELECTORS[webelem.Group.links], selectors = config.instance.get('hints.selectors', baseurl)
_prevnext_cb) link_selector = ','.join(selectors['links'])
browsertab.elements.find_css(link_selector, _prevnext_cb)

View File

@ -17,14 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Generic web element related code. """Generic web element related code."""
Module attributes:
Group: Enum for different kinds of groups.
SELECTORS: CSS selectors for different groups of elements.
"""
import enum
import collections.abc import collections.abc
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer
@ -36,25 +30,6 @@ from qutebrowser.mainwindow import mainwindow
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
Group = enum.Enum('Group', ['all', 'links', 'images', 'url', 'inputs'])
SELECTORS = {
Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, '
'frame, iframe, link, summary, [onclick], [onmousedown], '
'[role=link], [role=option], [role=button], img, '
# Angular 1 selectors
'[ng-click], [ngClick], [data-ng-click], [x-ng-click]'),
Group.links: 'a[href], area[href], link[href], [role=link][href]',
Group.images: 'img',
Group.url: '[src], [href]',
Group.inputs: ('input[type=text], input[type=email], input[type=url], '
'input[type=tel], input[type=number], '
'input[type=password], input[type=search], '
'input:not([type]), textarea'),
}
class Error(Exception): class Error(Exception):
"""Base class for WebElement errors.""" """Base class for WebElement errors."""

View File

@ -1162,6 +1162,69 @@ hints.scatter:
Ignored for number hints. Ignored for number hints.
hints.selectors:
default:
all:
- 'a'
- 'area'
- 'textarea'
- 'select'
- 'input:not([type="hidden"])'
- 'button'
- 'frame'
- 'iframe'
- 'img'
- 'link'
- 'summary'
- '[onclick]'
- '[onmousedown]'
- '[role="link"]'
- '[role="option"]'
- '[role="button"]'
- '[ng-click]'
- '[ngClick]'
- '[data-ng-click]'
- '[x-ng-click]'
links:
- 'a[href]'
- 'area[href]'
- 'link[href]'
- '[role="link"][href]'
images:
- 'img'
media:
- 'audio'
- 'img'
- 'video'
url:
- '[src]'
- '[href]'
inputs:
- 'input[type="text"]'
- 'input[type="date"]'
- 'input[type="datetime-local"]'
- 'input[type="email"]'
- 'input[type="month"]'
- 'input[type="number"]'
- 'input[type="password"]'
- 'input[type="search"]'
- 'input[type="tel"]'
- 'input[type="time"]'
- 'input[type="url"]'
- 'input[type="week"]'
- 'input:not([type])'
- 'textarea'
type:
name: Dict
keytype: String
valtype:
name: List
none_ok: true
valtype: String
supports_pattern: true
desc: CSS selectors used to determine which elements on a page should have
hints.
hints.uppercase: hints.uppercase:
default: false default: false
type: Bool type: Bool

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Custom hint groups</title>
</head>
<body>
<div class="clickable" onclick="console.log('beep!')">beep!</div>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<!-- target: hello.txt -->
<html>
<head>
<meta charset="utf-8">
<title>Javascript link</title>
</head>
<body>
<button id="link">Follow me via JS!</button>
<script type="text/javascript">
document.getElementById('link').addEventListener('click', function() {
window.location.href = '/data/hello.txt';
});
</script>
</body>
</html>

View File

@ -189,6 +189,30 @@ Feature: Using hints
# The actual check is already done above # The actual check is already done above
Then no crash should happen Then no crash should happen
Scenario: Error with invalid hint group
When I open data/hints/buttons.html
And I run :hint INVALID_GROUP
Then the error "Undefined hinting group 'INVALID_GROUP'!" should be shown
Scenario: Custom hint group
When I open data/hints/custom_group.html
And I set hints.selectors to {"custom":[".clickable"]}
And I hint with args "custom" and follow a
Then the javascript message "beep!" should be logged
Scenario: Custom hint group with URL pattern
When I open data/hints/custom_group.html
And I run :set -u *://*/data/hints/custom_group.html hints.selectors '{"custom": [".clickable"]}'
And I hint with args "custom" and follow a
Then the javascript message "beep!" should be logged
Scenario: Fallback to global value with URL pattern set
When I open data/hints/custom_group.html
And I set hints.selectors to {"custom":[".clickable"]}
And I run :set -u *://*/data/hints/custom_group.html hints.selectors '{"other": [".other"]}'
And I hint with args "custom" and follow a
Then the javascript message "beep!" should be logged
# https://github.com/qutebrowser/qutebrowser/issues/1613 # https://github.com/qutebrowser/qutebrowser/issues/1613
Scenario: Hinting inputs with padding Scenario: Hinting inputs with padding
When I open data/hints/input.html When I open data/hints/input.html

View File

@ -29,7 +29,7 @@ import pytest
from PyQt5.QtCore import QRect, QPoint, QUrl from PyQt5.QtCore import QRect, QPoint, QUrl
QWebElement = pytest.importorskip('PyQt5.QtWebKit').QWebElement QWebElement = pytest.importorskip('PyQt5.QtWebKit').QWebElement
from qutebrowser.browser import webelem, browsertab from qutebrowser.browser import browsertab
from qutebrowser.browser.webkit import webkitelem from qutebrowser.browser.webkit import webkitelem
from qutebrowser.misc import objects from qutebrowser.misc import objects
from qutebrowser.utils import usertypes from qutebrowser.utils import usertypes
@ -146,53 +146,46 @@ class SelectionAndFilterTests:
TESTS = [ TESTS = [
('<foo />', []), ('<foo />', []),
('<foo bar="baz"/>', []), ('<foo bar="baz"/>', []),
('<foo href="baz"/>', [webelem.Group.url]), ('<foo href="baz"/>', ['url']),
('<foo src="baz"/>', [webelem.Group.url]), ('<foo src="baz"/>', ['url']),
('<a />', [webelem.Group.all]), ('<a />', ['all']),
('<a href="foo" />', [webelem.Group.all, webelem.Group.links, ('<a href="foo" />', ['all', 'links', 'url']),
webelem.Group.url]), ('<a href="javascript://foo" />', ['all', 'links', 'url']),
('<a href="javascript://foo" />', [webelem.Group.all,
webelem.Group.links,
webelem.Group.url]),
('<area />', [webelem.Group.all]), ('<area />', ['all']),
('<area href="foo" />', [webelem.Group.all, webelem.Group.links, ('<area href="foo" />', ['all', 'links', 'url']),
webelem.Group.url]),
('<link />', [webelem.Group.all]), ('<link />', ['all']),
('<link href="foo" />', [webelem.Group.all, webelem.Group.links, ('<link href="foo" />', ['all', 'links', 'url']),
webelem.Group.url]),
('<textarea />', [webelem.Group.all, webelem.Group.inputs]), ('<textarea />', ['all', 'inputs']),
('<select />', [webelem.Group.all]), ('<select />', ['all']),
('<input />', [webelem.Group.all, webelem.Group.inputs]), ('<input />', ['all', 'inputs']),
('<input type="hidden" />', []), ('<input type="hidden" />', []),
('<input type="text" />', [webelem.Group.inputs, webelem.Group.all]), ('<input type="text" />', ['inputs', 'all']),
('<input type="email" />', [webelem.Group.inputs, webelem.Group.all]), ('<input type="email" />', ['inputs', 'all']),
('<input type="url" />', [webelem.Group.inputs, webelem.Group.all]), ('<input type="url" />', ['inputs', 'all']),
('<input type="tel" />', [webelem.Group.inputs, webelem.Group.all]), ('<input type="tel" />', ['inputs', 'all']),
('<input type="number" />', [webelem.Group.inputs, webelem.Group.all]), ('<input type="number" />', ['inputs', 'all']),
('<input type="password" />', [webelem.Group.inputs, ('<input type="password" />', ['inputs', 'all']),
webelem.Group.all]), ('<input type="search" />', ['inputs', 'all']),
('<input type="search" />', [webelem.Group.inputs, webelem.Group.all]),
('<button />', [webelem.Group.all]), ('<button />', ['all']),
('<button href="foo" />', [webelem.Group.all, webelem.Group.url]), ('<button href="foo" />', ['all', 'url']),
# We can't easily test <frame>/<iframe> as they vanish when setting # We can't easily test <frame>/<iframe> as they vanish when setting
# them via QWebFrame::setHtml... # them via QWebFrame::setHtml...
('<p onclick="foo" foo="bar"/>', [webelem.Group.all]), ('<p onclick="foo" foo="bar"/>', ['all']),
('<p onmousedown="foo" foo="bar"/>', [webelem.Group.all]), ('<p onmousedown="foo" foo="bar"/>', ['all']),
('<p role="option" foo="bar"/>', [webelem.Group.all]), ('<p role="option" foo="bar"/>', ['all']),
('<p role="button" foo="bar"/>', [webelem.Group.all]), ('<p role="button" foo="bar"/>', ['all']),
('<p role="button" href="bar"/>', [webelem.Group.all, ('<p role="button" href="bar"/>', ['all', 'url']),
webelem.Group.url]),
] ]
GROUPS = list(webelem.Group) GROUPS = ['all', 'links', 'images', 'url', 'inputs']
COMBINATIONS = list(itertools.product(TESTS, GROUPS)) COMBINATIONS = list(itertools.product(TESTS, GROUPS))
@ -215,11 +208,12 @@ class TestSelectorsAndFilters:
assert self.TESTS assert self.TESTS
@pytest.mark.parametrize('group, val, matching', TESTS) @pytest.mark.parametrize('group, val, matching', TESTS)
def test_selectors(self, webframe, group, val, matching): def test_selectors(self, webframe, group, val, matching, config_stub):
webframe.setHtml('<html><body>{}</body></html>'.format(val)) webframe.setHtml('<html><body>{}</body></html>'.format(val))
# Make sure setting HTML succeeded and there's a new element # Make sure setting HTML succeeded and there's a new element
assert len(webframe.findAllElements('*')) == 3 assert len(webframe.findAllElements('*')) == 3
elems = webframe.findAllElements(webelem.SELECTORS[group]) selector = ','.join(config_stub.val.hints.selectors[group])
elems = webframe.findAllElements(selector)
elems = [webkitelem.WebKitElement(e, tab=None) for e in elems] elems = [webkitelem.WebKitElement(e, tab=None) for e in elems]
assert bool(elems) == matching assert bool(elems) == matching