Merge branch 'webelem_perf' - huge hints/webelement performance improvements.
This commit is contained in:
commit
c6d55e7a6a
@ -507,19 +507,21 @@ class HintManager(QObject):
|
||||
ctx = HintContext()
|
||||
ctx.frames = webelem.get_child_frames(mainframe)
|
||||
for f in ctx.frames:
|
||||
for e in f.findAllElements(webelem.SELECTORS[group]):
|
||||
elems.append(webelem.WebElementWrapper(e))
|
||||
elems += f.findAllElements(webelem.SELECTORS[group])
|
||||
elems = [e for e in elems if webelem.is_visible(e, 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]
|
||||
filterfunc = webelem.FILTERS.get(group, lambda e: True)
|
||||
visible_elems = [e for e in elems if filterfunc(e) and
|
||||
e.is_visible(mainframe)]
|
||||
if not visible_elems:
|
||||
elems = [e for e in elems if filterfunc(e)]
|
||||
if not elems:
|
||||
raise cmdexc.CommandError("No elements found.")
|
||||
ctx.target = target
|
||||
ctx.baseurl = baseurl
|
||||
ctx.args = args
|
||||
message.instance().set_text(self.HINT_TEXTS[target])
|
||||
strings = self._hint_strings(visible_elems)
|
||||
for e, string in zip(visible_elems, strings):
|
||||
strings = self._hint_strings(elems)
|
||||
for e, string in zip(elems, strings):
|
||||
label = self._draw_label(e, string)
|
||||
ctx.elems[string] = ElemTuple(e, label)
|
||||
self._context = ctx
|
||||
|
@ -70,6 +70,8 @@ class WebElementWrapper(collections.abc.MutableMapping):
|
||||
"""A wrapper around QWebElement to make it more intelligent."""
|
||||
|
||||
def __init__(self, elem):
|
||||
if isinstance(elem, self.__class__):
|
||||
raise TypeError("Trying to wrap a wrapper!")
|
||||
if elem.isNull():
|
||||
raise IsNullError('{} is a null element!'.format(elem))
|
||||
self._elem = elem
|
||||
@ -98,15 +100,15 @@ class WebElementWrapper(collections.abc.MutableMapping):
|
||||
|
||||
method = getattr(self._elem, name)
|
||||
|
||||
@functools.wraps(method)
|
||||
def _wrapper(meth, *args, **kwargs):
|
||||
# pylint: disable=missing-docstring
|
||||
self._check_vanished()
|
||||
return meth(*args, **kwargs)
|
||||
|
||||
wrapper = functools.partial(_wrapper, method)
|
||||
functools.update_wrapper(wrapper, method)
|
||||
|
||||
# We used to do functools.update_wrapper here, but for some reason
|
||||
# when using hints with many links, this accounted for nearly 50%
|
||||
# of the time when profiling, which is unacceptable.
|
||||
setattr(self, name, wrapper)
|
||||
|
||||
def __str__(self):
|
||||
@ -162,58 +164,11 @@ class WebElementWrapper(collections.abc.MutableMapping):
|
||||
Return:
|
||||
True if the element is visible, False otherwise.
|
||||
"""
|
||||
self._check_vanished()
|
||||
# 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
|
||||
geometry = self._elem.geometry()
|
||||
if not geometry.isValid() and 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()
|
||||
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()
|
||||
elem_rect = self._elem.geometry()
|
||||
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_rect.isValid():
|
||||
visible_in_frame = framegeom.intersects(elem_rect)
|
||||
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_rect.topLeft())
|
||||
else:
|
||||
visible_in_frame = visible_on_screen
|
||||
return all([visible_on_screen, visible_in_frame])
|
||||
return is_visible(self._elem, mainframe)
|
||||
|
||||
def rect_on_view(self):
|
||||
"""Get the geometry of the element relative to the webview."""
|
||||
self._check_vanished()
|
||||
frame = self._elem.webFrame()
|
||||
rect = QRect(self._elem.geometry())
|
||||
while frame is not None:
|
||||
rect.translate(frame.geometry().topLeft())
|
||||
rect.translate(frame.scrollPosition() * -1)
|
||||
frame = frame.parentFrame()
|
||||
return rect
|
||||
return rect_on_view(self._elem)
|
||||
|
||||
def is_writable(self):
|
||||
"""Check whether an element is writable."""
|
||||
@ -379,3 +334,84 @@ def focus_elem(frame):
|
||||
"""
|
||||
elem = frame.findFirstElement(SELECTORS[Group.focus])
|
||||
return WebElementWrapper(elem)
|
||||
|
||||
|
||||
def rect_on_view(elem, elem_geometry=None):
|
||||
"""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.
|
||||
|
||||
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.
|
||||
"""
|
||||
if elem.isNull():
|
||||
raise IsNullError("Got called on a null element!")
|
||||
if elem_geometry is None:
|
||||
elem_geometry = elem.geometry()
|
||||
frame = elem.webFrame()
|
||||
rect = QRect(elem_geometry)
|
||||
while frame is not None:
|
||||
rect.translate(frame.geometry().topLeft())
|
||||
rect.translate(frame.scrollPosition() * -1)
|
||||
frame = frame.parentFrame()
|
||||
return rect
|
||||
|
||||
|
||||
def is_visible(elem, mainframe):
|
||||
"""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.
|
||||
mainframe: The QWebFrame in which the element should be visible.
|
||||
"""
|
||||
if elem.isNull():
|
||||
raise IsNullError("Got called on a null element!")
|
||||
# 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])
|
||||
|
Loading…
Reference in New Issue
Block a user