Refactor WebElement API, part 2

Now we don't get a crash, but not any hints either...
This commit is contained in:
Florian Bruhin 2016-07-27 16:27:38 +02:00
parent c5a91004f5
commit 193c755637
5 changed files with 43 additions and 54 deletions

View File

@ -633,6 +633,10 @@ class AbstractTab(QWidget):
def set_html(self, html, base_url):
raise NotImplementedError
def find_all_elements(self, selector):
"""Find all HTML elements matching a given selector."""
raise NotImplementedError
def __repr__(self):
try:
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode),

View File

@ -503,21 +503,13 @@ class CommandDispatcher:
if widget.backend == usertypes.Backend.QtWebEngine:
raise cmdexc.CommandError(":navigate prev/next is not "
"supported yet with QtWebEngine")
page = widget._widget.page() # pylint: disable=protected-access
frame = page.currentFrame()
if frame is None:
raise cmdexc.CommandError("No frame focused!")
else:
frame = None
hintmanager = objreg.get('hintmanager', scope='tab', tab='current')
if where == 'prev':
assert frame is not None
hintmanager.follow_prevnext(frame, url, prev=True, tab=tab,
hintmanager.follow_prevnext(widget, url, prev=True, tab=tab,
background=bg, window=window)
elif where == 'next':
assert frame is not None
hintmanager.follow_prevnext(frame, url, prev=False, tab=tab,
hintmanager.follow_prevnext(widget, url, prev=False, tab=tab,
background=bg, window=window)
elif where == 'up':
self._navigate_up(url, tab, bg, window)

View File

@ -80,7 +80,6 @@ class HintContext:
to_follow: The link to follow when enter is pressed.
args: Custom arguments for userscript/spawn
rapid: Whether to do rapid hinting.
mainframe: The main QWebFrame where we started hinting in.
tab: The WebTab object we started hinting in.
group: The group of web elements to hint.
"""
@ -94,7 +93,6 @@ class HintContext:
self.rapid = False
self.frames = []
self.args = []
self.mainframe = None
self.tab = None
self.group = None
@ -173,7 +171,7 @@ class HintManager(QObject):
"""Clean up after hinting."""
for elem in self._context.all_elems:
try:
elem.label.removeFromDocument()
elem.label.remove_from_document()
except webelem.IsNullError:
pass
text = self._get_text()
@ -384,7 +382,7 @@ class HintManager(QObject):
The newly created label element
"""
doc = elem.document_element()
body = doc.findFirst('body')
body = doc.find_first('body')
if body is None:
parent = doc
else:
@ -598,13 +596,12 @@ class HintManager(QObject):
qtutils.ensure_valid(url)
return url
def _find_prevnext(self, frame, prev=False):
def _find_prevnext(self, tab, prev=False):
"""Find a prev/next element in frame."""
# First check for <link rel="prev(ious)|next">
elems = frame.findAllElements(webelem.SELECTORS[webelem.Group.links])
elems = tab.find_all_elements(webelem.SELECTORS[webelem.Group.links])
rel_values = ('prev', 'previous') if prev else ('next')
for e in elems:
e = webelem.WebElementWrapper(e)
try:
rel_attr = e['rel']
except KeyError:
@ -614,9 +611,8 @@ class HintManager(QObject):
e.debug_text(), rel_attr))
return e
# Then check for regular links/buttons.
elems = frame.findAllElements(
elems = tab.find_all_elements(
webelem.SELECTORS[webelem.Group.prevnext])
elems = [webelem.WebElementWrapper(e) for e in elems]
filterfunc = webelem.FILTERS[webelem.Group.prevnext]
elems = [e for e in elems if filterfunc(e)]
@ -659,14 +655,9 @@ class HintManager(QObject):
def _init_elements(self):
"""Initialize the elements and labels based on the context set."""
elems = []
for f in self._context.frames:
elems += f.findAllElements(webelem.SELECTORS[self._context.group])
elems = [e for e in elems
if webelem.is_visible(e, self._context.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]
selector = webelem.SELECTORS[self._context.group]
elems = self._context.tab.find_all_elements(selector)
elems = [e for e in elems if e.is_visible()]
filterfunc = webelem.FILTERS.get(self._context.group, lambda e: True)
elems = [e for e in elems if filterfunc(e)]
if not elems:
@ -693,12 +684,12 @@ class HintManager(QObject):
# Do multi-word matching
return all(word in elemstr for word in filterstr.split())
def follow_prevnext(self, frame, baseurl, prev=False, tab=False,
def follow_prevnext(self, browsertab, baseurl, prev=False, tab=False,
background=False, window=False):
"""Click a "previous"/"next" element on the page.
Args:
frame: The frame where the element is in.
browsertab: The WebKitTab/WebEngineTab of the page.
baseurl: The base URL of the current tab.
prev: True to open a "previous" link, False to open a "next" link.
tab: True to open in a new tab, False for the current tab.
@ -706,7 +697,7 @@ class HintManager(QObject):
window: True to open in a new window, False for the current one.
"""
from qutebrowser.mainwindow import mainwindow
elem = self._find_prevnext(frame, prev)
elem = self._find_prevnext(browsertab, prev)
if elem is None:
raise cmdexc.CommandError("No {} links found!".format(
"prev" if prev else "forward"))
@ -789,11 +780,6 @@ class HintManager(QObject):
tab = tabbed_browser.currentWidget()
if tab is None:
raise cmdexc.CommandError("No WebView available yet!")
# FIXME:qtwebengine have a proper API for this
page = tab._widget.page() # pylint: disable=protected-access
mainframe = page.mainFrame()
if mainframe is None:
raise cmdexc.CommandError("No frame focused!")
mode_manager = objreg.get('mode-manager', scope='window',
window=self._win_id)
if mode_manager.mode == usertypes.KeyMode.hint:
@ -821,15 +807,13 @@ class HintManager(QObject):
self._context.baseurl = tabbed_browser.current_url()
except qtutils.QtValueError:
raise cmdexc.CommandError("No URL set for this page yet!")
self._context.frames = webelem.get_child_frames(mainframe)
self._context.tab = tab
self._context.args = args
self._context.mainframe = mainframe
self._context.group = group
self._init_elements()
message_bridge = objreg.get('message-bridge', scope='window',
window=self._win_id)
message_bridge.set_text(self._get_text())
self._connect_frame_signals()
modeman.enter(self._win_id, usertypes.KeyMode.hint,
'HintManager.start')
@ -1034,7 +1018,7 @@ class HintManager(QObject):
try:
if e.elem.frame() is None:
# This sometimes happens for some reason...
e.label.removeFromDocument()
e.label.remove_from_document()
continue
self._set_style_position(e.elem, e.label)
except webelem.IsNullError:

View File

@ -184,9 +184,9 @@ class WebElementWrapper(collections.abc.MutableMapping):
def set_inner_xml(self, xml):
"""Set the given inner XML."""
self._check_vanished()
self._elem.setInnerXml(text)
self._elem.setInnerXml(xml)
def remove(self):
def remove_from_document(self):
"""Remove the node from the document."""
self._check_vanished()
self._elem.removeFromDocument()
@ -196,16 +196,13 @@ class WebElementWrapper(collections.abc.MutableMapping):
self._check_vanished()
return self._elem.setStyleProperty(name, value)
def is_visible(self, mainframe):
def is_visible(self):
"""Check whether the element is currently visible on the screen.
Args:
mainframe: The main QWebFrame.
Return:
True if the element is visible, False otherwise.
"""
return is_visible(self._elem, mainframe)
return is_visible(self._elem)
def rect_on_view(self, **kwargs):
"""Get the geometry of the element relative to the webview."""
@ -438,14 +435,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.frame().zoomFactor()
zoom = elem.webFrame().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.frame()
frame = elem.webFrame()
while frame is not None:
# Translate to parent frames' position
# (scroll position is taken care of inside getClientRects)
@ -458,7 +455,7 @@ def rect_on_view(elem, *, elem_geometry=None, adjust_zoom=True, no_js=False):
geometry = elem.geometry()
else:
geometry = elem_geometry
frame = elem.frame()
frame = elem.webFrame()
rect = QRect(geometry)
while frame is not None:
rect.translate(frame.geometry().topLeft())
@ -466,7 +463,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.frame().zoomFactor()
zoom = elem.webFrame().zoomFactor()
if not config.get('ui', 'zoom-text-only'):
rect.moveTo(rect.left() / zoom, rect.top() / zoom)
rect.setWidth(rect.width() / zoom)
@ -474,7 +471,7 @@ def rect_on_view(elem, *, elem_geometry=None, adjust_zoom=True, no_js=False):
return rect
def is_visible(elem, mainframe):
def is_visible(elem):
"""Check if the given element is visible in the frame.
We need this as a standalone function (as opposed to a WebElementWrapper
@ -483,10 +480,10 @@ def is_visible(elem, mainframe):
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!")
mainframe = elem.webFrame()
# CSS attributes which hide an element
hidden_attributes = {
'visibility': 'hidden',
@ -510,7 +507,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.frame()
elem_frame = elem.webFrame()
framegeom = QRect(elem_frame.geometry())
if not framegeom.isValid():
visible_in_frame = False

View File

@ -30,7 +30,7 @@ from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtPrintSupport import QPrinter
from qutebrowser.browser import browsertab
from qutebrowser.browser.webkit import webview, tabhistory
from qutebrowser.browser.webkit import webview, tabhistory, webelem
from qutebrowser.utils import qtutils, objreg, usertypes, utils
@ -557,6 +557,18 @@ class WebKitTab(browsertab.AbstractTab):
def set_html(self, html, base_url):
self._widget.setHtml(html, base_url)
def find_all_elements(self, selector):
mainframe = self._widget.page().mainFrame()
if mainframe is None:
raise WebTabError("No frame focused!")
elems = []
frames = webelem.get_child_frames(mainframe)
for f in frames:
for elem in f.findAllElements(selector):
elems.append(webelem.WebElementWrapper(elem))
return elems
@pyqtSlot()
def _on_frame_load_finished(self):
"""Make sure we emit an appropriate status when loading finished.