Handle visibility of elements on screen correctly

This commit is contained in:
Florian Bruhin 2014-05-12 10:04:27 +02:00
parent f2f413e2d0
commit 50f31ca7cb
2 changed files with 34 additions and 40 deletions

View File

@ -244,7 +244,10 @@ class HintManager(QObject):
else: else:
target = self._target target = self._target
self.set_open_target.emit(Target[target]) self.set_open_target.emit(Target[target])
pos = webelem.pos_on_screen(elem) # FIXME Instead of clicking the center, we could have nicer heuristics.
# e.g. parse (-webkit-)border-radius correctly and click text fields at
# the bottom right, and everything else on the top left or so.
pos = webelem.rect_on_screen(elem).center()
logging.debug("Clicking on \"{}\" at {}/{}".format( logging.debug("Clicking on \"{}\" at {}/{}".format(
elem.toPlainText(), pos.x(), pos.y())) elem.toPlainText(), pos.x(), pos.y()))
events = [ events = [
@ -346,12 +349,12 @@ class HintManager(QObject):
return return
self.openurl.emit(link, newtab) self.openurl.emit(link, newtab)
def start(self, frame, baseurl, group=webelem.Group.all, def start(self, mainframe, baseurl, group=webelem.Group.all,
target=Target.normal): target=Target.normal):
"""Start hinting. """Start hinting.
Args: Args:
frame: The QWebFrame to place hints in. mainframe: The main QWebFrame.
baseurl: URL of the current page. baseurl: URL of the current page.
group: Which group of elements to hint. group: Which group of elements to hint.
target: What to do with the link. See attribute docstring. target: What to do with the link. See attribute docstring.
@ -361,19 +364,19 @@ class HintManager(QObject):
""" """
self._target = target self._target = target
self._baseurl = baseurl self._baseurl = baseurl
if frame is None: if mainframe is None:
# This should never happen since we check frame before calling # This should never happen since we check frame before calling
# start. But since we had a bug where frame is None in # start. But since we had a bug where frame is None in
# on_mode_left, we are extra careful here. # on_mode_left, we are extra careful here.
raise ValueError("start() was called with frame=None") raise ValueError("start() was called with frame=None")
self._frames = webelem.get_child_frames(frame) self._frames = webelem.get_child_frames(mainframe)
elems = [] elems = []
for f in self._frames: for f in self._frames:
elems += f.findAllElements(webelem.SELECTORS[group]) elems += f.findAllElements(webelem.SELECTORS[group])
filterfunc = webelem.FILTERS.get(group, lambda e: True) filterfunc = webelem.FILTERS.get(group, lambda e: True)
visible_elems = [] visible_elems = []
for e in elems: for e in elems:
if filterfunc(e) and webelem.is_visible(e): if filterfunc(e) and webelem.is_visible(e, mainframe):
visible_elems.append(e) visible_elems.append(e)
if not visible_elems: if not visible_elems:
message.error("No elements found.") message.error("No elements found.")

View File

@ -58,56 +58,47 @@ FILTERS = {
} }
def is_visible(elem): def is_visible(elem, mainframe):
"""Check whether the element is currently visible in its frame. """Check whether the element is currently visible on the screen.
Args: Args:
elem: The QWebElement to check. elem: The QWebElement to check.
mainframe: The main QWebFrame.
Return: Return:
True if the element is visible, False otherwise. True if the element is visible, False otherwise.
""" """
if elem.isNull(): if elem.isNull():
raise ValueError("Element is a null-element!") raise ValueError("Element is a null-element!")
## We're starting in the innermost (element) frame and check if every frame if (not elem.geometry().isValid()) and elem.geometry().x() == 0:
## is visible in its parent.
base = elem.webFrame()
parent = base.parentFrame()
while parent is not None:
parentgeom = parent.geometry()
parentgeom.translate(parent.scrollPosition())
if not parentgeom.intersects(base.geometry()):
return False
base = parent
parent = parent.parentFrame()
rect = elem.geometry()
## Now check if the element is visible in the frame.
if (not rect.isValid()) and rect.x() == 0:
# Most likely an invisible link # Most likely an invisible link
return False return False
frame = elem.webFrame() # First check if the element is visible on screen
framegeom = frame.geometry() elem_rect = rect_on_screen(elem)
framegeom.moveTo(0, 0) visible_on_screen = mainframe.geometry().intersects(elem_rect)
framegeom.translate(frame.scrollPosition()) # Then check if it's visible in its frame if it's not in the main frame.
if framegeom.intersects(rect): elem_frame = elem.webFrame()
return True if elem_frame.parentFrame() is not None:
return False framegeom = elem_frame.geometry()
framegeom.moveTo(0, 0)
framegeom.translate(elem_frame.scrollPosition())
visible_in_frame = framegeom.intersects(elem.geometry())
else:
visible_in_frame = visible_on_screen
return all([visible_on_screen, visible_in_frame])
def pos_on_screen(elem): def rect_on_screen(elem):
"""Get the position of the element on the screen.""" """Get the geometry of the element relative to the screen."""
# FIXME Instead of clicking the center, we could have nicer heuristics.
# e.g. parse (-webkit-)border-radius correctly and click text fields at
# the bottom right, and everything else on the top left or so.
frame = elem.webFrame() frame = elem.webFrame()
pos = elem.geometry().center() rect = elem.geometry()
while frame is not None: while frame is not None:
pos += frame.geometry().topLeft() rect.translate(frame.geometry().topLeft())
logging.debug("After adding frame pos: {}".format(pos)) logging.debug("After adding frame pos: {}".format(rect))
pos -= frame.scrollPosition() rect.translate(frame.scrollPosition() * -1)
logging.debug("After removing frame scrollpos: {}".format(pos)) logging.debug("After removing frame scrollpos: {}".format(rect))
frame = frame.parentFrame() frame = frame.parentFrame()
return pos return rect
def javascript_escape(text): def javascript_escape(text):