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.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.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.
|<<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.
@ -2574,6 +2575,73 @@ Type: <<types,Bool>>
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
Make characters in hint strings uppercase.

View File

@ -637,9 +637,8 @@ class HintManager(QObject):
star_args_optional=True, maxsplit=2)
@cmdutils.argument('win_id', win_id=True)
def start(self, # pylint: disable=keyword-arg-before-vararg
group=webelem.Group.all, target=Target.normal,
*args, win_id, mode=None, add_history=False, rapid=False,
first=False):
group='all', target=Target.normal, *args, win_id, mode=None,
add_history=False, rapid=False, first=False):
"""Start hinting.
Args:
@ -747,10 +746,25 @@ class HintManager(QObject):
raise cmdexc.CommandError("No URL set for this page yet!")
self._context.args = args
self._context.group = group
selector = webelem.SELECTORS[self._context.group]
selector = self._get_selector()
self._context.tab.elements.find_css(selector, self._start_cb,
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):
"""Return the currently active hinting mode (or None otherwise)."""
if self._context is None:

View File

@ -21,7 +21,6 @@
import posixpath
from qutebrowser.browser import webelem
from qutebrowser.config import config
from qutebrowser.utils import objreg, urlutils, log, message, qtutils
from qutebrowser.mainwindow import mainwindow
@ -147,5 +146,6 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
else:
browsertab.openurl(url)
browsertab.elements.find_css(webelem.SELECTORS[webelem.Group.links],
_prevnext_cb)
selectors = config.instance.get('hints.selectors', baseurl)
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
# 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
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
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):
"""Base class for WebElement errors."""

View File

@ -1162,6 +1162,69 @@ hints.scatter:
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:
default: false
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
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
Scenario: Hinting inputs with padding
When I open data/hints/input.html

View File

@ -29,7 +29,7 @@ import pytest
from PyQt5.QtCore import QRect, QPoint, QUrl
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.misc import objects
from qutebrowser.utils import usertypes
@ -146,53 +146,46 @@ class SelectionAndFilterTests:
TESTS = [
('<foo />', []),
('<foo bar="baz"/>', []),
('<foo href="baz"/>', [webelem.Group.url]),
('<foo src="baz"/>', [webelem.Group.url]),
('<foo href="baz"/>', ['url']),
('<foo src="baz"/>', ['url']),
('<a />', [webelem.Group.all]),
('<a href="foo" />', [webelem.Group.all, webelem.Group.links,
webelem.Group.url]),
('<a href="javascript://foo" />', [webelem.Group.all,
webelem.Group.links,
webelem.Group.url]),
('<a />', ['all']),
('<a href="foo" />', ['all', 'links', 'url']),
('<a href="javascript://foo" />', ['all', 'links', 'url']),
('<area />', [webelem.Group.all]),
('<area href="foo" />', [webelem.Group.all, webelem.Group.links,
webelem.Group.url]),
('<area />', ['all']),
('<area href="foo" />', ['all', 'links', 'url']),
('<link />', [webelem.Group.all]),
('<link href="foo" />', [webelem.Group.all, webelem.Group.links,
webelem.Group.url]),
('<link />', ['all']),
('<link href="foo" />', ['all', 'links', 'url']),
('<textarea />', [webelem.Group.all, webelem.Group.inputs]),
('<select />', [webelem.Group.all]),
('<textarea />', ['all', 'inputs']),
('<select />', ['all']),
('<input />', [webelem.Group.all, webelem.Group.inputs]),
('<input />', ['all', 'inputs']),
('<input type="hidden" />', []),
('<input type="text" />', [webelem.Group.inputs, webelem.Group.all]),
('<input type="email" />', [webelem.Group.inputs, webelem.Group.all]),
('<input type="url" />', [webelem.Group.inputs, webelem.Group.all]),
('<input type="tel" />', [webelem.Group.inputs, webelem.Group.all]),
('<input type="number" />', [webelem.Group.inputs, webelem.Group.all]),
('<input type="password" />', [webelem.Group.inputs,
webelem.Group.all]),
('<input type="search" />', [webelem.Group.inputs, webelem.Group.all]),
('<input type="text" />', ['inputs', 'all']),
('<input type="email" />', ['inputs', 'all']),
('<input type="url" />', ['inputs', 'all']),
('<input type="tel" />', ['inputs', 'all']),
('<input type="number" />', ['inputs', 'all']),
('<input type="password" />', ['inputs', 'all']),
('<input type="search" />', ['inputs', 'all']),
('<button />', [webelem.Group.all]),
('<button href="foo" />', [webelem.Group.all, webelem.Group.url]),
('<button />', ['all']),
('<button href="foo" />', ['all', 'url']),
# We can't easily test <frame>/<iframe> as they vanish when setting
# them via QWebFrame::setHtml...
('<p onclick="foo" foo="bar"/>', [webelem.Group.all]),
('<p onmousedown="foo" foo="bar"/>', [webelem.Group.all]),
('<p role="option" foo="bar"/>', [webelem.Group.all]),
('<p role="button" foo="bar"/>', [webelem.Group.all]),
('<p role="button" href="bar"/>', [webelem.Group.all,
webelem.Group.url]),
('<p onclick="foo" foo="bar"/>', ['all']),
('<p onmousedown="foo" foo="bar"/>', ['all']),
('<p role="option" foo="bar"/>', ['all']),
('<p role="button" foo="bar"/>', ['all']),
('<p role="button" href="bar"/>', ['all', 'url']),
]
GROUPS = list(webelem.Group)
GROUPS = ['all', 'links', 'images', 'url', 'inputs']
COMBINATIONS = list(itertools.product(TESTS, GROUPS))
@ -215,11 +208,12 @@ class TestSelectorsAndFilters:
assert self.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))
# Make sure setting HTML succeeded and there's a new element
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]
assert bool(elems) == matching