diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index c91cfca2b..4ba5c657f 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -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!") diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index be2b1bcef..a17d03972 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -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('') - 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( '{}{}'.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 diff --git a/qutebrowser/browser/webkit/webelem.py b/qutebrowser/browser/webkit/webelem.py index 97210bebd..9cacada2d 100644 --- a/qutebrowser/browser/webkit/webelem.py +++ b/qutebrowser/browser/webkit/webelem.py @@ -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