Merge remote-tracking branch 'origin/pr/3806' into test
This commit is contained in:
commit
6d4c8f5b13
@ -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="hidden"])]+
|
||||||
|
* +pass:[button]+
|
||||||
|
* +pass:[frame]+
|
||||||
|
* +pass:[iframe]+
|
||||||
|
* +pass:[img]+
|
||||||
|
* +pass:[link]+
|
||||||
|
* +pass:[summary]+
|
||||||
|
* +pass:[[onclick]]+
|
||||||
|
* +pass:[[onmousedown]]+
|
||||||
|
* +pass:[[role="link"]]+
|
||||||
|
* +pass:[[role="option"]]+
|
||||||
|
* +pass:[[role="button"]]+
|
||||||
|
* +pass:[[ng-click]]+
|
||||||
|
* +pass:[[ngClick]]+
|
||||||
|
* +pass:[[data-ng-click]]+
|
||||||
|
* +pass:[[x-ng-click]]+
|
||||||
|
- +pass:[images]+:
|
||||||
|
|
||||||
|
* +pass:[img]+
|
||||||
|
- +pass:[inputs]+:
|
||||||
|
|
||||||
|
* +pass:[input[type="text"]]+
|
||||||
|
* +pass:[input[type="date"]]+
|
||||||
|
* +pass:[input[type="datetime-local"]]+
|
||||||
|
* +pass:[input[type="email"]]+
|
||||||
|
* +pass:[input[type="month"]]+
|
||||||
|
* +pass:[input[type="number"]]+
|
||||||
|
* +pass:[input[type="password"]]+
|
||||||
|
* +pass:[input[type="search"]]+
|
||||||
|
* +pass:[input[type="tel"]]+
|
||||||
|
* +pass:[input[type="time"]]+
|
||||||
|
* +pass:[input[type="url"]]+
|
||||||
|
* +pass:[input[type="week"]]+
|
||||||
|
* +pass:[input:not([type])]+
|
||||||
|
* +pass:[textarea]+
|
||||||
|
- +pass:[links]+:
|
||||||
|
|
||||||
|
* +pass:[a[href]]+
|
||||||
|
* +pass:[area[href]]+
|
||||||
|
* +pass:[link[href]]+
|
||||||
|
* +pass:[[role="link"][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.
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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."""
|
||||||
|
@ -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
|
||||||
|
11
tests/end2end/data/hints/custom_group.html
Normal file
11
tests/end2end/data/hints/custom_group.html
Normal 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>
|
18
tests/end2end/data/hints/html/click_handler.html
Normal file
18
tests/end2end/data/hints/html/click_handler.html
Normal 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>
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user