diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 40c18915f..a0362c4c9 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -603,10 +603,11 @@ class AbstractTab(QWidget): def set_html(self, html, base_url): raise NotImplementedError - def find_all_elements(self, selector, *, only_visible=False): - """Find all HTML elements matching a given selector. + def find_all_elements(self, selector, callback, *, only_visible=False): + """Find all HTML elements matching a given selector async. Args: + callback: The callback to be called when the search finished. selector: The CSS selector to search for. only_visible: Only show elements which are visible on screen. """ diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 1b738e82b..97ebf54e9 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -603,43 +603,6 @@ class HintManager(QObject): qtutils.ensure_valid(url) return url - def _find_prevnext(self, tab, prev=False): - """Find a prev/next element in frame.""" - # First check for - elems = tab.find_all_elements(webelem.SELECTORS[webelem.Group.links]) - rel_values = ('prev', 'previous') if prev else ('next') - for e in elems: - try: - rel_attr = e['rel'] - except KeyError: - continue - if rel_attr in rel_values: - log.hints.debug("Found '{}' with rel={}".format( - e.debug_text(), rel_attr)) - return e - # Then check for regular links/buttons. - elems = tab.find_all_elements( - webelem.SELECTORS[webelem.Group.prevnext]) - filterfunc = webelem.FILTERS[webelem.Group.prevnext] - elems = [e for e in elems if filterfunc(e)] - - option = 'prev-regexes' if prev else 'next-regexes' - if not elems: - return None - for regex in config.get('hints', option): - log.hints.vdebug("== Checking regex '{}'.".format(regex.pattern)) - for e in elems: - text = str(e) - if not text: - continue - if regex.search(text): - log.hints.debug("Regex '{}' matched on '{}'.".format( - regex.pattern, text)) - return e - else: - log.hints.vdebug("No match on '{}'!".format(text)) - return None - def _check_args(self, target, *args): """Check the arguments passed to start() and raise if they're wrong. @@ -660,11 +623,95 @@ class HintManager(QObject): raise cmdexc.CommandError( "'args' is only allowed with target userscript/spawn.") - def _init_elements(self): + def _filter_matches(self, filterstr, elemstr): + """Return True if `filterstr` matches `elemstr`.""" + # Empty string and None always match + if not filterstr: + return True + filterstr = filterstr.casefold() + elemstr = elemstr.casefold() + # Do multi-word matching + return all(word in elemstr for word in filterstr.split()) + + def _find_prevnext(self, prev, elems): + """Find a prev/next element in the given list of elements.""" + # First check for + rel_values = ('prev', 'previous') if prev else ('next') + for e in elems: + if e.tag_name() != 'link' or 'rel' not in e: + continue + if e['rel'] in rel_values: + log.hints.debug("Found '{}' with rel={}".format( + e.debug_text(), e['rel'])) + return e + + # Then check for regular links/buttons. + filterfunc = webelem.FILTERS[webelem.Group.prevnext] + elems = [e for e in elems if e.tag_name() != 'link' and filterfunc(e)] + option = 'prev-regexes' if prev else 'next-regexes' + if not elems: + return None + for regex in config.get('hints', option): + log.hints.vdebug("== Checking regex '{}'.".format(regex.pattern)) + for e in elems: + text = str(e) + if not text: + continue + if regex.search(text): + log.hints.debug("Regex '{}' matched on '{}'.".format( + regex.pattern, text)) + return e + else: + log.hints.vdebug("No match on '{}'!".format(text)) + return None + + def follow_prevnext(self, browsertab, baseurl, prev=False, tab=False, + background=False, window=False): + """Click a "previous"/"next" element on the page. + + Args: + browsertab: The WebKitTab/WebEngineTab of the page. + baseurl: The base URL of the current tab. + prev: True to open a "previous" link, False to open a "next" link. + tab: True to open in a new tab, False for the current tab. + background: True to open in a background tab. + window: True to open in a new window, False for the current one. + """ + def _follow_prevnext_cb(elems): + elem = self._find_prevnext(prev, elems) + word = 'prev' if prev else 'forward' + + if elem is None: + message.error(self._win_id, "No {} links found!".format(word)) + return + url = self._resolve_url(elem, baseurl) + if url is None: + message.error(self._win_id, "No {} links found!".format(word)) + return + qtutils.ensure_valid(url) + + if window: + from qutebrowser.mainwindow import mainwindow + new_window = mainwindow.MainWindow() + new_window.show() + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=new_window.win_id) + tabbed_browser.tabopen(url, background=False) + elif tab: + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=self._win_id) + tabbed_browser.tabopen(url, background=background) + else: + browsertab = objreg.get('tab', scope='tab', + window=self._win_id, tab=self._tab_id) + browsertab.openurl(url) + + selector = ', '.join([webelem.SELECTORS[webelem.Group.links], + webelem.SELECTORS[webelem.Group.prevnext]]) + browsertab.find_all_elements(selector, _follow_prevnext_cb) + + def _start_cb(self, elems): """Initialize the elements and labels based on the context set.""" - selector = webelem.SELECTORS[self._context.group] - elems = self._context.tab.find_all_elements(selector, - only_visible=True) filterfunc = webelem.FILTERS.get(self._context.group, lambda e: True) elems = [e for e in elems if filterfunc(e)] if not elems: @@ -681,52 +728,13 @@ class HintManager(QObject): keyparser = keyparsers[usertypes.KeyMode.hint] keyparser.update_bindings(strings) - def _filter_matches(self, filterstr, elemstr): - """Return True if `filterstr` matches `elemstr`.""" - # Empty string and None always match - if not filterstr: - return True - filterstr = filterstr.casefold() - elemstr = elemstr.casefold() - # Do multi-word matching - return all(word in elemstr for word in filterstr.split()) - - def follow_prevnext(self, browsertab, baseurl, prev=False, tab=False, - background=False, window=False): - """Click a "previous"/"next" element on the page. - - Args: - browsertab: The WebKitTab/WebEngineTab of the page. - baseurl: The base URL of the current tab. - prev: True to open a "previous" link, False to open a "next" link. - tab: True to open in a new tab, False for the current tab. - background: True to open in a background tab. - window: True to open in a new window, False for the current one. - """ - from qutebrowser.mainwindow import mainwindow - elem = self._find_prevnext(browsertab, prev) - if elem is None: - raise cmdexc.CommandError("No {} links found!".format( - "prev" if prev else "forward")) - url = self._resolve_url(elem, baseurl) - if url is None: - raise cmdexc.CommandError("No {} links found!".format( - "prev" if prev else "forward")) - qtutils.ensure_valid(url) - if window: - new_window = mainwindow.MainWindow() - new_window.show() - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window=new_window.win_id) - tabbed_browser.tabopen(url, background=False) - elif tab: - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window=self._win_id) - tabbed_browser.tabopen(url, background=background) - else: - tab = objreg.get('tab', scope='tab', window=self._win_id, - tab=self._tab_id) - tab.openurl(url) + self._context.tab.contents_size_changed.connect( + self.on_contents_size_changed) + message_bridge = objreg.get('message-bridge', scope='window', + window=self._win_id) + message_bridge.set_text(self._get_text()) + modeman.enter(self._win_id, usertypes.KeyMode.hint, + 'HintManager.start') @cmdutils.register(instance='hintmanager', scope='tab', name='hint', star_args_optional=True, maxsplit=2, @@ -817,13 +825,9 @@ class HintManager(QObject): self._context.tab = tab self._context.args = args self._context.group = group - self._init_elements() - tab.contents_size_changed.connect(self.on_contents_size_changed) - message_bridge = objreg.get('message-bridge', scope='window', - window=self._win_id) - message_bridge.set_text(self._get_text()) - modeman.enter(self._win_id, usertypes.KeyMode.hint, - 'HintManager.start') + selector = webelem.SELECTORS[self._context.group] + self._context.tab.find_all_elements(selector, self._start_cb, + only_visible=True) def handle_partial_key(self, keystr): """Handle a new partial keypress.""" diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index ec98e3baa..37d69b491 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -411,9 +411,9 @@ class WebEngineTab(browsertab.AbstractTab): def clear_ssl_errors(self): log.stub() - def find_all_elements(self, selector, *, only_visible=False): + def find_all_elements(self, selector, callback, *, only_visible=False): log.stub() - return [] + callback([]) def _connect_signals(self): view = self._widget diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index bf50d668e..5dce30abd 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -558,7 +558,7 @@ class WebKitTab(browsertab.AbstractTab): def set_html(self, html, base_url): self._widget.setHtml(html, base_url) - def find_all_elements(self, selector, *, only_visible=False): + def find_all_elements(self, selector, callback, *, only_visible=False): mainframe = self._widget.page().mainFrame() if mainframe is None: raise browsertab.WebTabError("No frame focused!") @@ -572,7 +572,7 @@ class WebKitTab(browsertab.AbstractTab): if only_visible: elems = [e for e in elems if e.is_visible(mainframe)] - return elems + callback(elems) @pyqtSlot() def _on_frame_load_finished(self):