diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 55e8c37a3..16390c0c0 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -207,6 +207,7 @@ |<>|Comma-separated list of regular expressions to use for 'next' links. |<>|Comma-separated list of regular expressions to use for 'prev' links. |<>|Scatter hint key chains (like Vimium) or not (like dwb). +|<>|CSS selectors used to determine which elements on a page should have hints. |<>|Make characters in hint strings uppercase. |<>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session. |<>|Allow Escape to quit the crash reporter. @@ -2574,6 +2575,73 @@ Type: <> 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: <> + +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 Make characters in hint strings uppercase. diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 333999356..dfeef48ed 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -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: diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py index 257ce6fe0..f3da0c18e 100644 --- a/qutebrowser/browser/navigate.py +++ b/qutebrowser/browser/navigate.py @@ -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) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index f1e510581..681033e6d 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -17,14 +17,8 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""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.""" diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index cdf9a2370..0d4bed4a0 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -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 diff --git a/tests/end2end/data/hints/custom_group.html b/tests/end2end/data/hints/custom_group.html new file mode 100644 index 000000000..380d10dda --- /dev/null +++ b/tests/end2end/data/hints/custom_group.html @@ -0,0 +1,11 @@ + + + + + + Custom hint groups + + +
beep!
+ + diff --git a/tests/end2end/data/hints/html/click_handler.html b/tests/end2end/data/hints/html/click_handler.html new file mode 100644 index 000000000..43338a519 --- /dev/null +++ b/tests/end2end/data/hints/html/click_handler.html @@ -0,0 +1,18 @@ + + + + + + + + Javascript link + + + + + + diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index fd4465daa..2cb6dee4c 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -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 diff --git a/tests/unit/browser/webkit/test_webkitelem.py b/tests/unit/browser/webkit/test_webkitelem.py index 971900e94..0bfb61066 100644 --- a/tests/unit/browser/webkit/test_webkitelem.py +++ b/tests/unit/browser/webkit/test_webkitelem.py @@ -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 = [ ('', []), ('', []), - ('', [webelem.Group.url]), - ('', [webelem.Group.url]), + ('', ['url']), + ('', ['url']), - ('', [webelem.Group.all]), - ('', [webelem.Group.all, webelem.Group.links, - webelem.Group.url]), - ('', [webelem.Group.all, - webelem.Group.links, - webelem.Group.url]), + ('', ['all']), + ('', ['all', 'links', 'url']), + ('', ['all', 'links', 'url']), - ('', [webelem.Group.all]), - ('', [webelem.Group.all, webelem.Group.links, - webelem.Group.url]), + ('', ['all']), + ('', ['all', 'links', 'url']), - ('', [webelem.Group.all]), - ('', [webelem.Group.all, webelem.Group.links, - webelem.Group.url]), + ('', ['all']), + ('', ['all', 'links', 'url']), - ('