Refactor QWebElement API, part 1

This commit is contained in:
Florian Bruhin 2016-07-27 15:11:06 +02:00
parent 243ba02f5f
commit c764733472
3 changed files with 94 additions and 74 deletions

View File

@ -1460,15 +1460,7 @@ class CommandDispatcher:
text: The new text to insert.
"""
try:
if elem.is_content_editable():
log.misc.debug("Filling element {} via setPlainText.".format(
elem.debug_text()))
elem.setPlainText(text)
else:
log.misc.debug("Filling element {} via javascript.".format(
elem.debug_text()))
text = webelem.javascript_escape(text)
elem.evaluateJavaScript("this.value='{}'".format(text))
elem.set_text(text)
except webelem.IsNullError:
raise cmdexc.CommandError("Element vanished while editing!")

View File

@ -335,16 +335,16 @@ class HintManager(QObject):
def _is_hidden(self, elem):
"""Check if the element is hidden via display=none."""
display = elem.styleProperty('display', QWebElement.InlineStyle)
display = elem.style_property('display', QWebElement.InlineStyle)
return display == 'none'
def _show_elem(self, elem):
"""Show a given element."""
elem.setStyleProperty('display', 'inline !important')
elem.set_style_property('display', 'inline !important')
def _hide_elem(self, elem):
"""Hide a given element."""
elem.setStyleProperty('display', 'none !important')
elem.set_style_property('display', 'none !important')
def _set_style_properties(self, elem, label):
"""Set the hint CSS on the element given.
@ -373,7 +373,7 @@ class HintManager(QObject):
attrs.append(('text-transform', 'none !important'))
for k, v in attrs:
label.setStyleProperty(k, v)
label.set_style_property(k, v)
self._set_style_position(elem, label)
def _set_style_position(self, elem, label):
@ -389,8 +389,8 @@ class HintManager(QObject):
top = rect.y()
log.hints.vdebug("Drawing label '{!r}' at {}/{} for element '{!r}' "
"(no_js: {})".format(label, left, top, elem, no_js))
label.setStyleProperty('left', '{}px !important'.format(left))
label.setStyleProperty('top', '{}px !important'.format(top))
label.set_style_property('left', '{}px !important'.format(left))
label.set_style_property('top', '{}px !important'.format(top))
def _draw_label(self, elem, string):
"""Draw a hint label over an element.
@ -402,22 +402,16 @@ class HintManager(QObject):
Return:
The newly created label element
"""
doc = elem.webFrame().documentElement()
# It seems impossible to create an empty QWebElement for which isNull()
# is false so we can work with it.
# As a workaround, we use appendInside() with markup as argument, and
# then use lastChild() to get a reference to it.
# See: http://stackoverflow.com/q/7364852/2085149
doc = elem.document_element()
body = doc.findFirst('body')
if not body.isNull():
parent = body
else:
if body is None:
parent = doc
parent.appendInside('<span></span>')
label = webelem.WebElementWrapper(parent.lastChild())
else:
parent = body
label = parent.create_inside('span')
label['class'] = 'qutehint'
self._set_style_properties(elem, label)
label.setPlainText(string)
label.set_text(string)
return label
def _show_url_error(self):
@ -490,7 +484,7 @@ class HintManager(QObject):
self.mouse_event.emit(evt)
if elem.is_text_input() and elem.is_editable():
QTimer.singleShot(0, functools.partial(
elem.webFrame().page().triggerAction,
elem.frame().page().triggerAction,
QWebPage.MoveToEndOfDocument))
QTimer.singleShot(0, self.stop_hinting.emit)
@ -559,7 +553,7 @@ class HintManager(QObject):
download_manager = objreg.get('download-manager', scope='window',
window=self._win_id)
download_manager.get(url, page=elem.webFrame().page(),
download_manager.get(url, page=elem.frame().page(),
prompt_download_directory=prompt)
def _call_userscript(self, elem, context):
@ -878,7 +872,7 @@ class HintManager(QObject):
matched = string[:len(keystr)]
rest = string[len(keystr):]
match_color = config.get('colors', 'hints.fg.match')
elem.label.setInnerXml(
elem.label.set_inner_xml(
'<font color="{}">{}</font>{}'.format(
match_color, matched, rest))
if self._is_hidden(elem.label):
@ -913,7 +907,7 @@ class HintManager(QObject):
strings = self._hint_strings(elems)
self._context.elems = {}
for elem, string in zip(elems, strings):
elem.label.setInnerXml(string)
elem.label.set_inner_xml(string)
self._context.elems[string] = elem
keyparsers = objreg.get('keyparsers', scope='window',
window=self._win_id)
@ -1017,7 +1011,7 @@ class HintManager(QObject):
Target.spawn: self._spawn,
}
elem = self._context.elems[keystr].elem
if elem.webFrame() is None:
if elem.frame() is None:
message.error(self._win_id,
"This element has no webframe.",
immediately=True)
@ -1042,7 +1036,7 @@ class HintManager(QObject):
self.filter_hints(None)
# Undo keystring highlighting
for string, elem in self._context.elems.items():
elem.label.setInnerXml(string)
elem.label.set_inner_xml(string)
handler()
@cmdutils.register(instance='hintmanager', scope='tab', hide=True,
@ -1068,7 +1062,7 @@ class HintManager(QObject):
log.hints.debug("Contents size changed...!")
for e in self._context.all_elems:
try:
if e.elem.webFrame() is None:
if e.elem.frame() is None:
# This sometimes happens for some reason...
e.label.removeFromDocument()
continue

View File

@ -28,7 +28,6 @@ Module attributes:
"""
import collections.abc
import functools
from PyQt5.QtCore import QRect, QUrl
from PyQt5.QtWebKit import QWebElement
@ -83,40 +82,6 @@ class WebElementWrapper(collections.abc.MutableMapping):
if elem.isNull():
raise IsNullError('{} is a null element!'.format(elem))
self._elem = elem
for name in ['addClass', 'appendInside', 'appendOutside',
'attributeNS', 'classes', 'clone', 'document',
'encloseContentsWith', 'encloseWith',
'evaluateJavaScript', 'findAll', 'findFirst',
'firstChild', 'geometry', 'hasAttributeNS',
'hasAttributes', 'hasClass', 'hasFocus', 'lastChild',
'localName', 'namespaceUri', 'nextSibling', 'parent',
'prefix', 'prependInside', 'prependOutside',
'previousSibling', 'removeAllChildren',
'removeAttributeNS', 'removeClass', 'removeFromDocument',
'render', 'replace', 'setAttributeNS', 'setFocus',
'setInnerXml', 'setOuterXml', 'setPlainText',
'setStyleProperty', 'styleProperty', 'tagName',
'takeFromDocument', 'toInnerXml', 'toOuterXml',
'toggleClass', 'webFrame', '__eq__', '__ne__']:
# We don't wrap some methods for which we have better alternatives:
# - Mapping access for attributeNames/hasAttribute/setAttribute/
# attribute/removeAttribute.
# - isNull is checked automagically.
# - str(...) instead of toPlainText
# For the rest, we create a wrapper which checks if the element is
# null.
method = getattr(self._elem, name)
def _wrapper(meth, *args, **kwargs):
self._check_vanished()
return meth(*args, **kwargs)
wrapper = functools.partial(_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):
self._check_vanished()
@ -162,6 +127,75 @@ class WebElementWrapper(collections.abc.MutableMapping):
if self._elem.isNull():
raise IsNullError('Element {} vanished!'.format(self._elem))
def frame(self):
"""Get the main frame of this element."""
# FIXME:qtwebengine how to get rid of this?
self._check_vanished()
return self._elem.webFrame()
def geometry(self):
"""Get the geometry for this element."""
self._check_vanished()
return self._elem.geometry()
def document_element(self):
"""Get the document element of this element."""
self._check_vanished()
elem = self._elem.webFrame().documentElement()
return WebElementWrapper(elem)
def create_inside(self, tagname):
"""Append the given element inside the current one."""
# It seems impossible to create an empty QWebElement for which isNull()
# is false so we can work with it.
# As a workaround, we use appendInside() with markup as argument, and
# then use lastChild() to get a reference to it.
# See: http://stackoverflow.com/q/7364852/2085149
self._check_vanished()
self._elem.appendInside('<{}></{}>'.format(tagname, tagname))
return WebElementWrapper(self._elem.lastChild())
def find_first(self, selector):
"""Find the first child based on the given CSS selector."""
self._check_vanished()
elem = self._elem.findFirst(selector)
if elem.isNull():
return None
return WebElementWrapper(elem)
def style_property(self, name, strategy):
"""Get the element style resolved with the given strategy."""
self._check_vanished()
return self._elem.styleProperty(name, strategy)
def set_text(self, text):
"""Set the given plain text."""
self._check_vanished()
if self.is_content_editable():
log.misc.debug("Filling element {} via set_text.".format(
self.debug_text()))
self._elem.setPlainText(text)
else:
log.misc.debug("Filling element {} via javascript.".format(
self.debug_text()))
text = javascript_escape(text)
self._elem.evaluateJavaScript("this.value='{}'".format(text))
def set_inner_xml(self, xml):
"""Set the given inner XML."""
self._check_vanished()
self._elem.setInnerXml(text)
def remove(self):
"""Remove the node from the document."""
self._check_vanished()
self._elem.removeFromDocument()
def set_style_property(self, name, value):
"""Set the element style."""
self._check_vanished()
return self._elem.setStyleProperty(name, value)
def is_visible(self, mainframe):
"""Check whether the element is currently visible on the screen.
@ -404,14 +438,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.webFrame().zoomFactor()
zoom = elem.frame().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()
frame = elem.frame()
while frame is not None:
# Translate to parent frames' position
# (scroll position is taken care of inside getClientRects)
@ -424,7 +458,7 @@ def rect_on_view(elem, *, elem_geometry=None, adjust_zoom=True, no_js=False):
geometry = elem.geometry()
else:
geometry = elem_geometry
frame = elem.webFrame()
frame = elem.frame()
rect = QRect(geometry)
while frame is not None:
rect.translate(frame.geometry().topLeft())
@ -432,7 +466,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.webFrame().zoomFactor()
zoom = elem.frame().zoomFactor()
if not config.get('ui', 'zoom-text-only'):
rect.moveTo(rect.left() / zoom, rect.top() / zoom)
rect.setWidth(rect.width() / zoom)
@ -476,7 +510,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.webFrame()
elem_frame = elem.frame()
framegeom = QRect(elem_frame.geometry())
if not framegeom.isValid():
visible_in_frame = False