Revert "Make webelem.rect_on_view work async"

This reverts commit 4e11613d2df064b138532c18f88bbf278c64f347.

We can actually make this synchronous just fine by collecting that
information when searching for the elements...
This commit is contained in:
Florian Bruhin 2016-08-17 16:55:23 +02:00
parent e6d6302958
commit 62db0095d1
3 changed files with 69 additions and 89 deletions

View File

@ -124,8 +124,8 @@ class HintLabel(QLabel):
self.hide()
return
no_js = config.get('hints', 'find-implementation') != 'javascript'
self.elem.rect_on_view(no_js=no_js,
callback=lambda r: self.move(r.x(), r.y()))
rect = self.elem.rect_on_view(no_js=no_js)
self.move(rect.x(), rect.y())
def cleanup(self):
"""Clean up this element and hide it."""
@ -221,59 +221,54 @@ class HintActions(QObject):
else:
target_mapping[Target.tab] = usertypes.ClickTarget.tab
def click_cb(rect):
"""Actually click the element.
# Click the center of the largest square fitting into the top/left
# corner of the rectangle, this will help if part of the <a> element
# is hidden behind other elements
# https://github.com/The-Compiler/qutebrowser/issues/1005
rect = elem.rect_on_view()
if rect.width() > rect.height():
rect.setWidth(rect.height())
else:
rect.setHeight(rect.width())
pos = rect.center()
Click the center of the largest square fitting into the top/left
corner of the rectangle, this will help if part of the <a>
element is hidden behind other elements
https://github.com/The-Compiler/qutebrowser/issues/1005
"""
if rect.width() > rect.height():
rect.setWidth(rect.height())
else:
rect.setHeight(rect.width())
pos = rect.center()
action = "Hovering" if context.target == Target.hover else "Clicking"
log.hints.debug("{} on '{}' at position {}".format(
action, elem.debug_text(), pos))
action = "Hovering" if context.target == Target.hover else "Clicking"
log.hints.debug("{} on '{}' at position {}".format(
action, elem.debug_text(), pos))
self.start_hinting.emit(target_mapping[context.target])
if context.target in [Target.tab, Target.tab_fg, Target.tab_bg,
Target.window]:
modifiers = Qt.ControlModifier
else:
modifiers = Qt.NoModifier
events = [
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
Qt.NoModifier),
self.start_hinting.emit(target_mapping[context.target])
if context.target in [Target.tab, Target.tab_fg, Target.tab_bg,
Target.window]:
modifiers = Qt.ControlModifier
else:
modifiers = Qt.NoModifier
events = [
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
Qt.NoModifier),
]
if context.target != Target.hover:
events += [
QMouseEvent(QEvent.MouseButtonPress, pos, Qt.LeftButton,
Qt.LeftButton, modifiers),
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
Qt.NoButton, modifiers),
]
if context.target != Target.hover:
events += [
QMouseEvent(QEvent.MouseButtonPress, pos, Qt.LeftButton,
Qt.LeftButton, modifiers),
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
Qt.NoButton, modifiers),
]
if context.target in [Target.normal, Target.current]:
# Set the pre-jump mark ', so we can jump back here after following
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
tabbed_browser.set_mark("'")
if context.target in [Target.normal, Target.current]:
# Set the pre-jump mark ', so we can jump back here after following
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
tabbed_browser.set_mark("'")
if context.target == Target.current:
elem.remove_blank_target()
for evt in events:
self.mouse_event.emit(evt)
if elem.is_text_input() and elem.is_editable():
QTimer.singleShot(0, functools.partial(
elem.frame().page().triggerAction,
QWebPage.MoveToEndOfDocument))
QTimer.singleShot(0, self.stop_hinting.emit)
elem.rect_on_view(callback=click_cb)
if context.target == Target.current:
elem.remove_blank_target()
for evt in events:
self.mouse_event.emit(evt)
if elem.is_text_input() and elem.is_editable():
QTimer.singleShot(0, functools.partial(
elem.frame().page().triggerAction,
QWebPage.MoveToEndOfDocument))
QTimer.singleShot(0, self.stop_hinting.emit)
def yank(self, url, context):
"""Yank an element to the clipboard or primary selection.

View File

@ -199,22 +199,8 @@ class WebKitElement(webelem.AbstractWebElement):
frame = frame.parentFrame()
return rect
def _rect_on_view_sync(self, *, elem_geometry=None, no_js=False):
"""Synchronous part of rect_on_view."""
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:
rect = self._rect_on_view_js()
if rect is not None:
return rect
# No suitable rects found via JS, try via the QWebElement API
return self._rect_on_view_python(elem_geometry)
def rect_on_view(self, *, callback, **kwargs):
"""Get the geometry of the element relative to the webview (async).
def rect_on_view(self, *, elem_geometry=None, 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
@ -230,15 +216,21 @@ class WebKitElement(webelem.AbstractWebElement):
Calling QWebElement::geometry is rather expensive so
we want to avoid doing it twice.
no_js: Fall back to the Python implementation
callback: Gets called with the found QRect.
"""
self._check_vanished()
callback(self._rect_on_view_sync(**kwargs))
# First try getting the element rect via JS, as that's usually more
# accurate
if elem_geometry is None and not no_js:
rect = self._rect_on_view_js()
if rect is not None:
return rect
# No suitable rects found via JS, try via the QWebElement API
return self._rect_on_view_python(elem_geometry)
def is_visible(self, mainframe):
"""Check if the given element is visible in the given frame."""
# FIXME:qtwebengine can we get rid of this with
# find_all_elements(only_visible=True)?
self._check_vanished()
# CSS attributes which hide an element
hidden_attributes = {
@ -253,7 +245,7 @@ class WebKitElement(webelem.AbstractWebElement):
# Most likely an invisible link
return False
# First check if the element is visible on screen
elem_rect = self._rect_on_view_sync(elem_geometry=elem_geometry)
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)

View File

@ -269,7 +269,7 @@ class TestWebKitElement:
lambda e: e.outer_xml(),
lambda e: e.tag_name(),
lambda e: e.run_js_async(''),
lambda e: e.rect_on_view(callback=None),
lambda e: e.rect_on_view(),
lambda e: e.is_visible(None),
], ids=['str', 'getitem', 'setitem', 'delitem', 'contains', 'iter', 'len',
'frame', 'geometry', 'style_property', 'text', 'set_text',
@ -708,24 +708,22 @@ class TestRectOnView:
# unusable geometry via getElementRects
{'length': '1', '0': {'width': 0, 'height': 0, 'x': 0, 'y': 0}},
])
def test_simple(self, callback_checker, stubs, js_rect):
def test_simple(self, stubs, js_rect):
geometry = QRect(5, 5, 4, 4)
frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100))
elem = get_webelem(geometry, frame, js_rect_return=js_rect)
elem.rect_on_view(callback=callback_checker.callback)
callback_checker.check(QRect(5, 5, 4, 4))
assert elem.rect_on_view() == QRect(5, 5, 4, 4)
@pytest.mark.parametrize('js_rect', [None, {}])
def test_scrolled(self, callback_checker, stubs, js_rect):
def test_scrolled(self, stubs, js_rect):
geometry = QRect(20, 20, 4, 4)
frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100),
scroll=QPoint(10, 10))
elem = get_webelem(geometry, frame, js_rect_return=js_rect)
elem.rect_on_view(callback=callback_checker.callback)
callback_checker.check(QRect(20 - 10, 20 - 10, 4, 4))
assert elem.rect_on_view() == QRect(20 - 10, 20 - 10, 4, 4)
@pytest.mark.parametrize('js_rect', [None, {}])
def test_iframe(self, callback_checker, stubs, js_rect):
def test_iframe(self, stubs, js_rect):
"""Test an element in an iframe.
0, 0 200, 0
@ -746,32 +744,27 @@ class TestRectOnView:
assert frame.geometry().contains(iframe.geometry())
elem = get_webelem(QRect(20, 90, 10, 10), iframe,
js_rect_return=js_rect)
elem.rect_on_view(callback=callback_checker.callback)
callback_checker.check(QRect(20, 10 + 90, 10, 10))
assert elem.rect_on_view() == QRect(20, 10 + 90, 10, 10)
@pytest.mark.parametrize('js_rect', [None, {}])
def test_passed_geometry(self, callback_checker, stubs, js_rect):
def test_passed_geometry(self, stubs, js_rect):
"""Make sure geometry isn't called when a geometry is passed."""
frame = stubs.FakeWebFrame(QRect(0, 0, 200, 200))
elem = get_webelem(frame=frame, js_rect_return=js_rect)
rect = QRect(10, 20, 30, 40)
elem.rect_on_view(elem_geometry=rect,
callback=callback_checker.callback)
callback_checker.check(rect)
assert elem.rect_on_view(elem_geometry=rect) == rect
assert not elem._elem.geometry.called
@pytest.mark.parametrize('js_rect', [None, {}])
@pytest.mark.parametrize('zoom_text_only', [True, False])
def test_zoomed(self, callback_checker, stubs, config_stub, js_rect,
zoom_text_only):
def test_zoomed(self, stubs, config_stub, js_rect, zoom_text_only):
"""Make sure the coordinates are adjusted when zoomed."""
config_stub.data = {'ui': {'zoom-text-only': zoom_text_only}}
geometry = QRect(10, 10, 4, 4)
frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100), zoom=0.5)
elem = get_webelem(geometry, frame, js_rect_return=js_rect,
zoom_text_only=zoom_text_only)
elem.rect_on_view(callback=callback_checker.callback)
callback_checker.check(QRect(10, 10, 4, 4))
assert elem.rect_on_view() == QRect(10, 10, 4, 4)
class TestGetChildFrames: