diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 8ca437e2d..cadc6a31d 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -633,6 +633,10 @@ class AbstractTab(QWidget): def set_html(self, html, base_url): raise NotImplementedError + def find_all_elements(self, selector): + """Find all HTML elements matching a given selector.""" + raise NotImplementedError + def __repr__(self): try: url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode), diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 4ba5c657f..5a81cce5a 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -503,21 +503,13 @@ class CommandDispatcher: if widget.backend == usertypes.Backend.QtWebEngine: raise cmdexc.CommandError(":navigate prev/next is not " "supported yet with QtWebEngine") - page = widget._widget.page() # pylint: disable=protected-access - frame = page.currentFrame() - if frame is None: - raise cmdexc.CommandError("No frame focused!") - else: - frame = None hintmanager = objreg.get('hintmanager', scope='tab', tab='current') if where == 'prev': - assert frame is not None - hintmanager.follow_prevnext(frame, url, prev=True, tab=tab, + hintmanager.follow_prevnext(widget, url, prev=True, tab=tab, background=bg, window=window) elif where == 'next': - assert frame is not None - hintmanager.follow_prevnext(frame, url, prev=False, tab=tab, + hintmanager.follow_prevnext(widget, url, prev=False, tab=tab, background=bg, window=window) elif where == 'up': self._navigate_up(url, tab, bg, window) diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 63b6ec031..2a1716bcf 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -80,7 +80,6 @@ class HintContext: to_follow: The link to follow when enter is pressed. args: Custom arguments for userscript/spawn rapid: Whether to do rapid hinting. - mainframe: The main QWebFrame where we started hinting in. tab: The WebTab object we started hinting in. group: The group of web elements to hint. """ @@ -94,7 +93,6 @@ class HintContext: self.rapid = False self.frames = [] self.args = [] - self.mainframe = None self.tab = None self.group = None @@ -173,7 +171,7 @@ class HintManager(QObject): """Clean up after hinting.""" for elem in self._context.all_elems: try: - elem.label.removeFromDocument() + elem.label.remove_from_document() except webelem.IsNullError: pass text = self._get_text() @@ -384,7 +382,7 @@ class HintManager(QObject): The newly created label element """ doc = elem.document_element() - body = doc.findFirst('body') + body = doc.find_first('body') if body is None: parent = doc else: @@ -598,13 +596,12 @@ class HintManager(QObject): qtutils.ensure_valid(url) return url - def _find_prevnext(self, frame, prev=False): + def _find_prevnext(self, tab, prev=False): """Find a prev/next element in frame.""" # First check for - elems = frame.findAllElements(webelem.SELECTORS[webelem.Group.links]) + elems = tab.find_all_elements(webelem.SELECTORS[webelem.Group.links]) rel_values = ('prev', 'previous') if prev else ('next') for e in elems: - e = webelem.WebElementWrapper(e) try: rel_attr = e['rel'] except KeyError: @@ -614,9 +611,8 @@ class HintManager(QObject): e.debug_text(), rel_attr)) return e # Then check for regular links/buttons. - elems = frame.findAllElements( + elems = tab.find_all_elements( webelem.SELECTORS[webelem.Group.prevnext]) - elems = [webelem.WebElementWrapper(e) for e in elems] filterfunc = webelem.FILTERS[webelem.Group.prevnext] elems = [e for e in elems if filterfunc(e)] @@ -659,14 +655,9 @@ class HintManager(QObject): def _init_elements(self): """Initialize the elements and labels based on the context set.""" - elems = [] - for f in self._context.frames: - elems += f.findAllElements(webelem.SELECTORS[self._context.group]) - elems = [e for e in elems - if webelem.is_visible(e, self._context.mainframe)] - # We wrap the elements late for performance reasons, as wrapping 1000s - # of elements (with ~50 methods each) just takes too much time... - elems = [webelem.WebElementWrapper(e) for e in elems] + selector = webelem.SELECTORS[self._context.group] + elems = self._context.tab.find_all_elements(selector) + elems = [e for e in elems if e.is_visible()] filterfunc = webelem.FILTERS.get(self._context.group, lambda e: True) elems = [e for e in elems if filterfunc(e)] if not elems: @@ -693,12 +684,12 @@ class HintManager(QObject): # Do multi-word matching return all(word in elemstr for word in filterstr.split()) - def follow_prevnext(self, frame, baseurl, prev=False, tab=False, + def follow_prevnext(self, browsertab, baseurl, prev=False, tab=False, background=False, window=False): """Click a "previous"/"next" element on the page. Args: - frame: The frame where the element is in. + 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. @@ -706,7 +697,7 @@ class HintManager(QObject): window: True to open in a new window, False for the current one. """ from qutebrowser.mainwindow import mainwindow - elem = self._find_prevnext(frame, prev) + elem = self._find_prevnext(browsertab, prev) if elem is None: raise cmdexc.CommandError("No {} links found!".format( "prev" if prev else "forward")) @@ -789,11 +780,6 @@ class HintManager(QObject): tab = tabbed_browser.currentWidget() if tab is None: raise cmdexc.CommandError("No WebView available yet!") - # FIXME:qtwebengine have a proper API for this - page = tab._widget.page() # pylint: disable=protected-access - mainframe = page.mainFrame() - if mainframe is None: - raise cmdexc.CommandError("No frame focused!") mode_manager = objreg.get('mode-manager', scope='window', window=self._win_id) if mode_manager.mode == usertypes.KeyMode.hint: @@ -821,15 +807,13 @@ class HintManager(QObject): self._context.baseurl = tabbed_browser.current_url() except qtutils.QtValueError: raise cmdexc.CommandError("No URL set for this page yet!") - self._context.frames = webelem.get_child_frames(mainframe) + self._context.tab = tab self._context.args = args - self._context.mainframe = mainframe self._context.group = group self._init_elements() message_bridge = objreg.get('message-bridge', scope='window', window=self._win_id) message_bridge.set_text(self._get_text()) - self._connect_frame_signals() modeman.enter(self._win_id, usertypes.KeyMode.hint, 'HintManager.start') @@ -1034,7 +1018,7 @@ class HintManager(QObject): try: if e.elem.frame() is None: # This sometimes happens for some reason... - e.label.removeFromDocument() + e.label.remove_from_document() continue self._set_style_position(e.elem, e.label) except webelem.IsNullError: diff --git a/qutebrowser/browser/webkit/webelem.py b/qutebrowser/browser/webkit/webelem.py index 9cacada2d..3da6227ac 100644 --- a/qutebrowser/browser/webkit/webelem.py +++ b/qutebrowser/browser/webkit/webelem.py @@ -184,9 +184,9 @@ class WebElementWrapper(collections.abc.MutableMapping): def set_inner_xml(self, xml): """Set the given inner XML.""" self._check_vanished() - self._elem.setInnerXml(text) + self._elem.setInnerXml(xml) - def remove(self): + def remove_from_document(self): """Remove the node from the document.""" self._check_vanished() self._elem.removeFromDocument() @@ -196,16 +196,13 @@ class WebElementWrapper(collections.abc.MutableMapping): self._check_vanished() return self._elem.setStyleProperty(name, value) - def is_visible(self, mainframe): + def is_visible(self): """Check whether the element is currently visible on the screen. - Args: - mainframe: The main QWebFrame. - Return: True if the element is visible, False otherwise. """ - return is_visible(self._elem, mainframe) + return is_visible(self._elem) def rect_on_view(self, **kwargs): """Get the geometry of the element relative to the webview.""" @@ -438,14 +435,14 @@ def rect_on_view(elem, *, elem_geometry=None, adjust_zoom=True, no_js=False): height = rect.get("height", 0) if width > 1 and height > 1: # fix coordinates according to zoom level - zoom = elem.frame().zoomFactor() + zoom = elem.webFrame().zoomFactor() if not config.get('ui', 'zoom-text-only') and adjust_zoom: rect["left"] *= zoom rect["top"] *= zoom width *= zoom height *= zoom rect = QRect(rect["left"], rect["top"], width, height) - frame = elem.frame() + frame = elem.webFrame() while frame is not None: # Translate to parent frames' position # (scroll position is taken care of inside getClientRects) @@ -458,7 +455,7 @@ def rect_on_view(elem, *, elem_geometry=None, adjust_zoom=True, no_js=False): geometry = elem.geometry() else: geometry = elem_geometry - frame = elem.frame() + frame = elem.webFrame() rect = QRect(geometry) while frame is not None: rect.translate(frame.geometry().topLeft()) @@ -466,7 +463,7 @@ def rect_on_view(elem, *, elem_geometry=None, adjust_zoom=True, no_js=False): frame = frame.parentFrame() # We deliberately always adjust the zoom here, even with adjust_zoom=False if elem_geometry is None: - zoom = elem.frame().zoomFactor() + zoom = elem.webFrame().zoomFactor() if not config.get('ui', 'zoom-text-only'): rect.moveTo(rect.left() / zoom, rect.top() / zoom) rect.setWidth(rect.width() / zoom) @@ -474,7 +471,7 @@ def rect_on_view(elem, *, elem_geometry=None, adjust_zoom=True, no_js=False): return rect -def is_visible(elem, mainframe): +def is_visible(elem): """Check if the given element is visible in the frame. We need this as a standalone function (as opposed to a WebElementWrapper @@ -483,10 +480,10 @@ def is_visible(elem, mainframe): Args: elem: The QWebElement to check. - mainframe: The QWebFrame in which the element should be visible. """ if elem.isNull(): raise IsNullError("Got called on a null element!") + mainframe = elem.webFrame() # CSS attributes which hide an element hidden_attributes = { 'visibility': 'hidden', @@ -510,7 +507,7 @@ def is_visible(elem, mainframe): visible_on_screen = mainframe_geometry.contains(elem_rect.topLeft()) # Then check if it's visible in its frame if it's not in the main # frame. - elem_frame = elem.frame() + elem_frame = elem.webFrame() framegeom = QRect(elem_frame.geometry()) if not framegeom.isValid(): visible_in_frame = False diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 7cc127e16..dca126f91 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -30,7 +30,7 @@ from PyQt5.QtWebKit import QWebSettings from PyQt5.QtPrintSupport import QPrinter from qutebrowser.browser import browsertab -from qutebrowser.browser.webkit import webview, tabhistory +from qutebrowser.browser.webkit import webview, tabhistory, webelem from qutebrowser.utils import qtutils, objreg, usertypes, utils @@ -557,6 +557,18 @@ class WebKitTab(browsertab.AbstractTab): def set_html(self, html, base_url): self._widget.setHtml(html, base_url) + def find_all_elements(self, selector): + mainframe = self._widget.page().mainFrame() + if mainframe is None: + raise WebTabError("No frame focused!") + + elems = [] + frames = webelem.get_child_frames(mainframe) + for f in frames: + for elem in f.findAllElements(selector): + elems.append(webelem.WebElementWrapper(elem)) + return elems + @pyqtSlot() def _on_frame_load_finished(self): """Make sure we emit an appropriate status when loading finished.