From ffdaf8d470f075b8fc5c67609d13a5a8afacb115 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 27 Jul 2016 16:51:24 +0200 Subject: [PATCH] Move webelem.is_visible/.rect_on_view to methods --- qutebrowser/browser/webkit/webelem.py | 257 ++++++++++++-------------- 1 file changed, 116 insertions(+), 141 deletions(-) diff --git a/qutebrowser/browser/webkit/webelem.py b/qutebrowser/browser/webkit/webelem.py index b8ffa1f86..5fd21397a 100644 --- a/qutebrowser/browser/webkit/webelem.py +++ b/qutebrowser/browser/webkit/webelem.py @@ -200,18 +200,6 @@ class WebElementWrapper(collections.abc.MutableMapping): self._check_vanished() return self._elem.setStyleProperty(name, value) - def is_visible(self): - """Check whether the element is currently visible on the screen. - - Return: - True if the element is visible, False otherwise. - """ - return is_visible(self._elem) - - def rect_on_view(self, **kwargs): - """Get the geometry of the element relative to the webview.""" - return rect_on_view(self._elem, **kwargs) - def is_writable(self): """Check whether an element is writable.""" self._check_vanished() @@ -339,6 +327,122 @@ class WebElementWrapper(collections.abc.MutableMapping): self._check_vanished() return utils.compact_text(self._elem.toOuterXml(), 500) + def rect_on_view(self, *, elem_geometry=None, adjust_zoom=True, no_js=False): + """Get the geometry of the element relative to the webview. + + Uses the getClientRects() JavaScript method to obtain the collection of + rectangles containing the element and returns the first rectangle which is + large enough (larger than 1px times 1px). If all rectangles returned by + getClientRects() are too small, falls back to elem.rect_on_view(). + + Skipping of small rectangles is due to elements containing other + elements with "display:block" style, see + https://github.com/The-Compiler/qutebrowser/issues/1298 + + Args: + elem_geometry: The geometry of the element, or None. + Calling QWebElement::geometry is rather expensive so we + want to avoid doing it twice. + adjust_zoom: Whether to adjust the element position based on the + current zoom level. + no_js: Fall back to the Python implementation + """ + self._check_vanished() + + # First try getting the element rect via JS, as that's usually more + # accurate + if elem_geometry is None and not no_js: + rects = self._elem.evaluateJavaScript("this.getClientRects()") + text = utils.compact_text(self._elem.toOuterXml(), 500) + log.hints.vdebug("Client rectangles of element '{}': {}".format(text, + rects)) + for i in range(int(rects.get("length", 0))): + rect = rects[str(i)] + width = rect.get("width", 0) + height = rect.get("height", 0) + if width > 1 and height > 1: + # fix coordinates according to zoom level + zoom = self._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 = self._elem.webFrame() + while frame is not None: + # Translate to parent frames' position + # (scroll position is taken care of inside getClientRects) + rect.translate(frame.geometry().topLeft()) + frame = frame.parentFrame() + return rect + + # No suitable rects found via JS, try via the QWebElement API + if elem_geometry is None: + geometry = self._elem.geometry() + else: + geometry = elem_geometry + frame = self._elem.webFrame() + rect = QRect(geometry) + while frame is not None: + rect.translate(frame.geometry().topLeft()) + rect.translate(frame.scrollPosition() * -1) + frame = frame.parentFrame() + # We deliberately always adjust the zoom here, even with adjust_zoom=False + if elem_geometry is None: + zoom = self._elem.webFrame().zoomFactor() + if not config.get('ui', 'zoom-text-only'): + rect.moveTo(rect.left() / zoom, rect.top() / zoom) + rect.setWidth(rect.width() / zoom) + rect.setHeight(rect.height() / zoom) + return rect + + def is_visible(self): + """Check if the given element is visible in the frame.""" + self._check_vanished() + mainframe = self._elem.webFrame() + # CSS attributes which hide an element + hidden_attributes = { + 'visibility': 'hidden', + 'display': 'none', + } + for k, v in hidden_attributes.items(): + if self._elem.styleProperty(k, QWebElement.ComputedStyle) == v: + return False + elem_geometry = self._elem.geometry() + if not elem_geometry.isValid() and elem_geometry.x() == 0: + # Most likely an invisible link + return False + # First check if the element is visible on screen + elem_rect = self.rect_on_view(elem_geometry=elem_geometry) + mainframe_geometry = mainframe.geometry() + if elem_rect.isValid(): + visible_on_screen = mainframe_geometry.intersects(elem_rect) + else: + # We got an invalid rectangle (width/height 0/0 probably), but this + # can still be a valid link. + 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 = self._elem.webFrame() + framegeom = QRect(elem_frame.geometry()) + if not framegeom.isValid(): + visible_in_frame = False + elif elem_frame.parentFrame() is not None: + framegeom.moveTo(0, 0) + framegeom.translate(elem_frame.scrollPosition()) + if elem_geometry.isValid(): + visible_in_frame = framegeom.intersects(elem_geometry) + else: + # We got an invalid rectangle (width/height 0/0 probably), but + # this can still be a valid link. + visible_in_frame = framegeom.contains(elem_geometry.topLeft()) + else: + visible_in_frame = visible_on_screen + return all([visible_on_screen, visible_in_frame]) + + + def javascript_escape(text): """Escape values special to javascript in strings. @@ -398,132 +502,3 @@ def focus_elem(frame): return WebElementWrapper(elem) -def rect_on_view(elem, *, elem_geometry=None, adjust_zoom=True, no_js=False): - """Get the geometry of the element relative to the webview. - - We need this as a standalone function (as opposed to a WebElementWrapper - method) because we want to run is_visible before wrapping when hinting for - performance reasons. - - Uses the getClientRects() JavaScript method to obtain the collection of - rectangles containing the element and returns the first rectangle which is - large enough (larger than 1px times 1px). If all rectangles returned by - getClientRects() are too small, falls back to elem.rect_on_view(). - - Skipping of small rectangles is due to elements containing other - elements with "display:block" style, see - https://github.com/The-Compiler/qutebrowser/issues/1298 - - Args: - elem: The QWebElement to get the rect for. - elem_geometry: The geometry of the element, or None. - Calling QWebElement::geometry is rather expensive so we - want to avoid doing it twice. - adjust_zoom: Whether to adjust the element position based on the - current zoom level. - no_js: Fall back to the Python implementation - """ - if elem.isNull(): - raise IsNullError("Got called on a null element!") - - # First try getting the element rect via JS, as that's usually more - # accurate - if elem_geometry is None and not no_js: - rects = elem.evaluateJavaScript("this.getClientRects()") - text = utils.compact_text(elem.toOuterXml(), 500) - log.hints.vdebug("Client rectangles of element '{}': {}".format(text, - rects)) - for i in range(int(rects.get("length", 0))): - rect = rects[str(i)] - width = rect.get("width", 0) - height = rect.get("height", 0) - if width > 1 and height > 1: - # fix coordinates according to zoom level - 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.webFrame() - while frame is not None: - # Translate to parent frames' position - # (scroll position is taken care of inside getClientRects) - rect.translate(frame.geometry().topLeft()) - frame = frame.parentFrame() - return rect - - # No suitable rects found via JS, try via the QWebElement API - if elem_geometry is None: - geometry = elem.geometry() - else: - geometry = elem_geometry - frame = elem.webFrame() - rect = QRect(geometry) - while frame is not None: - rect.translate(frame.geometry().topLeft()) - rect.translate(frame.scrollPosition() * -1) - frame = frame.parentFrame() - # We deliberately always adjust the zoom here, even with adjust_zoom=False - if elem_geometry is None: - 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) - rect.setHeight(rect.height() / zoom) - return rect - - -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 - method) because we want to check this before wrapping when hinting for - performance reasons. - - Args: - elem: The QWebElement to check. - """ - if elem.isNull(): - raise IsNullError("Got called on a null element!") - mainframe = elem.webFrame() - # CSS attributes which hide an element - hidden_attributes = { - 'visibility': 'hidden', - 'display': 'none', - } - for k, v in hidden_attributes.items(): - if elem.styleProperty(k, QWebElement.ComputedStyle) == v: - return False - elem_geometry = elem.geometry() - if not elem_geometry.isValid() and elem_geometry.x() == 0: - # Most likely an invisible link - return False - # First check if the element is visible on screen - elem_rect = rect_on_view(elem, elem_geometry=elem_geometry) - mainframe_geometry = mainframe.geometry() - if elem_rect.isValid(): - visible_on_screen = mainframe_geometry.intersects(elem_rect) - else: - # We got an invalid rectangle (width/height 0/0 probably), but this - # can still be a valid link. - 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.webFrame() - framegeom = QRect(elem_frame.geometry()) - if not framegeom.isValid(): - visible_in_frame = False - elif elem_frame.parentFrame() is not None: - framegeom.moveTo(0, 0) - framegeom.translate(elem_frame.scrollPosition()) - if elem_geometry.isValid(): - visible_in_frame = framegeom.intersects(elem_geometry) - else: - # We got an invalid rectangle (width/height 0/0 probably), but - # this can still be a valid link. - visible_in_frame = framegeom.contains(elem_geometry.topLeft()) - else: - visible_in_frame = visible_on_screen - return all([visible_on_screen, visible_in_frame])